List<データ型>などは見かけることも多いかと思います。
データ型にはInteger型やString型などを指定することができて、様々なデータ型でも同じように処理することが出来て便利です。
この機能をジェネリクスと呼び、自作のクラスやメソッドでも利用できます。
この記事では、ジェネリクスについて以下の内容で解説していきます。
- ジェネリクスとは
- クラスでジェネリクスを使う方法
- ワイルドカードでextendsを使う場合
- ワイルドカードでsuperを使う場合
- メソッドでジェネリクスを使う方法
今回はジェネリクスについて、使い方をわかりやすく解説します!
なお、Javaの記事については、こちらにまとめています。
ジェネリクス(Generics・総称型)とは
ジェネリクスとは「<>」記号で囲まれたデータ型名をクラスやメソッドに付けることで、Integer型やString型などの様々な型に対応する汎用的なクラスやメソッドを作る機能のことです。
ジェネリクスを使わないと、データ型の不一致で実行時にエラーが発生する場合があります。
簡単な例を下記に示します。
class ClassSample{ private Object o; public ClassSample(Object o){ this.o = o; } public Object getO(){ return o; } } public class Main { public static void main(String[] args) { ClassSample cs = new ClassSample("Hello"); Integer i = (Integer) cs.getO(); System.out.println(i); } }
実行結果:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at Main.main(Main.java:18)
この例はコンパイルは出来ますが、実行時にエラーが発生します。
この例のような実行時のエラーを防ぐことができる機能がジェネリクスです。
クラスでジェネリクスを使う方法
先ほどの例の問題はクラスでジェネリクスを使えば解決します。
それではジェネリクスを使う方法をみてみましょう。
class ClassSample<T>{ private T t; public ClassSample(T t){ this.t = t; } public T getT(){ return t; } } public class Main { public static void main(String[] args) { // String型として利用可能 ClassSample<String> cs1 = new ClassSample<String>("Hello"); String str = cs1.getT(); System.out.println(str); // Integer型として利用可能 ClassSample<Integer> cs2 = new ClassSample<Integer>(1); Integer i = cs2.getT(); System.out.println(i); } }
実行結果:
Hello 1
この例ではClassSampleクラス名に続けて「<T>」を記述しています。
この「<T>」を記述することで、データ型の指定がObject型からT型(型変数)に変更されます。
MainクラスのmainメソッドでClassSampleクラスをインスタンス化する際には、データ型を「<>」記号の中で指定する必要があります。
この例ではまずString型で指定しています。
ジェネリクスの機能を使うことで、ClassSampleクラスのオブジェクトを呼び出すgetTメソッドはキャストすることなく、指定した型に値を代入することができています。
これにより実行時にデータ型の不一致でエラーが発生することなく、String型やInteger型など様々な型で使用することができます。
ワイルドカードでextendsを使う場合
また、ワイルドカードを使うことでデータ型を後で宣言することができます。
ワイルドカードは、データ型を宣言せずに一時的に変数を保持する場合に使用します。
サンプルコードで確認しましょう。
class ClassSample<T>{ private T t; public ClassSample(T t){ this.t = t; } public T getT(){ return t; } } public class Main { public static void main(String[] args) { // Integer型として利用可能 ClassSample<Integer> cs2 = new ClassSample<Integer>(1); Integer i = cs2.getT(); System.out.println(i); // ワイルドカードを使用 Number型として利用可能 ClassSample<? extends Number> cs3; cs3 = cs2; Number n = cs3.getT(); System.out.println(n); } }
実行結果:
1 1
このサンプルコードでは、cs3インスタンスを生成する際にワイルドカードを表す「?」記号を使用して「<? extends Number>」としています。
IntegerクラスはNumberクラスのサブクラスですので、「<? extends Number>」で定義したcs3インスタンスにcs2インスタンスを代入することができます。
するとcs3インスタンスでもgetTメソッドを使用することができ、cs2インスタンスでInteger型に指定した「1」をcs3インスタンスではNumber型として使用できるようになります。
ワイルドカードでsuperを使う場合
逆にNumber型で指定したcs4インスタンスをInteger型で指定したcs5インスタンスに代入する場合、getTメソッドはObject型として使用します。
この場合は「extends」句の代わりに「super」句を用います。
サンプルコードで確認しましょう。
class ClassSample<T>{ private T t; public ClassSample(T t){ this.t = t; } public T getT(){ return t; } } public class Main { public static void main(String[] args) { // ワイルドカードを使用 Object型として利用可能 ClassSample<Number> cs4 = new ClassSample<Number>(1); ClassSample<? super Integer> cs5; cs5 = cs4; Object o = cs5.getT(); System.out.println(o); } }
実行結果:
1
メソッドでジェネリクスを使う方法
ジェネリクスはメソッドに対しても使用することができます。
メソッドを定義する際に戻り値の型名であるTの前に「<T>」句を記述する必要があります。
こちらもサンプルコードでみていきましょう。
public class Main { public static void main(String[] args) { String str = getT("Hello"); System.out.println(str); Integer i = getT(1); System.out.println(i); } private static <T> T getT(T t){ return t; } }
実行結果:
Hello 1
この例ではgetTメソッドの引数と戻り値にT型を指定しています。
クラスでジェネリクスを使用する場合と同様に、getTメソッドをキャストする必要はありません。
まとめ
ここでは、ジェネリクスについてクラスやメソッドでの使い方について説明しました。
Integer型やString型のように様々なデータ型で同じような処理をしたい場合にデータ型毎にそれぞれ処理を記述する必要がなくなります。
また、実行時のエラーが発生する危険もなくなります。
このように便利な機能ですので、使いこなすことができるようにこの記事を何度も参考にして下さいね!