目次
Javaのジェネリクス(Generics/総称型)とは?
ジェネリクスとは、クラスやメソッドにデータ型名を「<>」記号でくくることで、String型やInteger型などの指定した型に対応する多用途型なクラスやメソッドを作る機能のことをいいます。
以下のような、リストに入れる型の限定などがよく見られるジェネリクスです。
List<String> arr = new ArrayList<String>();
通常、Listにはさまざまなものを入れられますが、 「<String>」を付けると入れられる型がStringに限定されます。
ジェネリクスを使うメリットは?
たとえば、上記のリストにIntegerを入れると、Stringではないのでコンパイルエラーになります。
そのため、ジェネリクスを使うとコンパイルエラーが出るのでコードの記述ミスが見つかりやすいです。
また、型を限定しているためコード上で何の型を扱っているかわかりやすくなっています。
ジェネリクスのネーミング慣例
特に決められてはいないのですが、ジェネリクスの変数名は基本的に1字で定義します。
また、以下のような慣例があるので覚えておくとよいでしょう。
- E:Element(要素)
- K:Key(キー)
- T:Type(タイプ)
- V:Value(値)
- N:Number(数値)
- S,U:2,3番目
クラスでのジェネリクスの書き方
クラスでジェネリクスを使う方法を見ていきましょう。
class GenericSample<T>{ private T value; public GenericSample(T val){ this.value = val; } public T getValue(){ return value; } } public class Main { public static void main(String[] args) { // String型のジェネリクス GenericSample<String> cstr = new GenericSample<String>("Hello ポテパン!"); String str = cstr.getValue(); System.out.println(str); // Integer型のジェネリクス GenericSample<Integer> cint = new GenericSample<Integer>(100); Integer inum = cint.getValue(); System.out.println(inum); } }
上記のサンプルコードは、GenericSampleクラスに「<T>」を記述しました。
「<T>」を記述すると、「Object型」から「T型(型変数)」にデータ型の指定が変わります。
mainメソッド内でGenericSampleクラスをインスタンス化する場合には、データ型を「<>」記号で指定します。
このサンプルコードでは<String>と<Integer>で指定していますね。
getValueメソッドは、ジェネリクスを使っているため型変換(キャスト)することなく、指定した型に値を代入できています。
そのため、データ型が一致しないエラーが出ずに複数の型で使用可能になっているのです。
このサンプルコードを実行すると、以下の実行結果が得られます。
実行結果↓
Hello ポテパン! 100
ジェネリクスのワイルドカード型とは?
ジェネリクスのワイルドカード型とは、型に「?」記号が付いているジェネリクス型です。
ワイルドカードを利用すると、プログラムが実行されるまで型が不明なものとして表せます。
ワイルドカード型には、「非境界ワイルドカード型」と「境界ワイルドカード型」の2種類あり、それぞれ以下のように分けられます。
- 非境界ワイルドカード型 (unbounded wildcard type)
- 境界ワイルドカード型 (bounded wildcard type)
ひとつずつ解説しますね。
1.非境界ワイルドカード型
非境界ワイルドカード型は、型が「?」のみのジェネリクス型を言います。
実型のパラメータが決まっていない場合などに利用されます。
具体的なコードを見てみましょう。
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { checkAnyKindOfList(new ArrayList()); checkAnyKindOfList(new ArrayList()); } public static void checkAnyKindOfList(List<?> list) { Object o = list.get(0); } }
checkAnyKindOfListメソッドは、引数にどんな制約が付いたListでも渡せます。
しかし、何が渡されるかは不明です。
checkAnyKindOfListメソッドでは、要素の取り出しなど「要素の制約がわからなくても行える操作」であれば実行可能です。
反対に、要素の追加など「要素の制約がわかっていないと行えない操作」はできません。
このサンプルコードを実行すると、java.lang.IndexOutOfBoundsExceptionエラーが発生します。(サイズが設定されていないためです)
2.境界ワイルドカード型
境界ワイルドカード型は、型にある程度の幅(上下限)をもたせて宣言できるジェネリクス型を言います。
境界ワイルドカード型には「上限付き境界ワイルドカード」と「下限付き境界ワイルドカード」の2種類あります。
それぞれの使い方を見ていきましょう。
1.上限付き境界ワイルドカード(extends)
上限付き境界ワイルドカードでは、「?」と「extends」を用います。
サンプルコードで動きを確認しましょう。
class GenericSample<T>{ private T value; public GenericSample(T val){ this.value = val; } public T getValue(){ return value; } } public class Main { public static void main(String[] args) { // Integer型のジェネリクス GenericSample<Integer> gnum1 = new GenericSample<Integer>(100); Integer inum = gnum1.getValue(); System.out.println(inum); // Number型のジェネリクス(ワイルドカードを使用) GenericSample<? extends Number> gnum2; gnum2 = gnum1; Number num = gnum2.getValue(); System.out.println(num); } }
GenericSampleクラスの型変数Tは不明な型です。
そこで「<? extends Number>」を記述すれば、NumberクラスまたはNumberクラスのサブクラスのみOKという境界が設定されます。
実行結果↓
100 100
2.下限付き境界ワイルドカード(super)
下限付き境界ワイルドカードでは、「?」と「super」を用います。
class GenericSample<T>{ private T value; public GenericSample(T val){ this.value = val; } public T getValue(){ return value; } } public class Main { public static void main(String[] args) { // Object型のジェネリクス GenericSample<Object> gobj = new GenericSample<Object>("ポテパン"); // String型のジェネリクス(ワイルドカードを使用) GenericSample<? super String> gstr; gstr = gobj; Object obj = gstr.getValue(); System.out.println(obj); } }
「<? uper String>」を記述して、StringクラスまたはStringクラスの親クラスのみOKという境界が設定されます。
実行結果↓
ポテパン
メソッドでのジェネリクスの書き方
ジェネリクスは、クラスだけではなくメソッドでも使用可能です。
メソッドで利用する場合は、戻り値の型名Tの前に「<T>」を記述します。
サンプルコードで動きを確認してみましょう。
public class Main { public static void main(String[] args) { String str = getValue("ポテパンとジェネリクスを学ぶ"); System.out.println(str); Number num = getValue(300); System.out.println(num); } private static <T> T getValue(T t){ return t; } }
getValueメソッドの引数と戻り値にT型を指定します。
また、クラスと同様にgetValueメソッドをキャストの必要はありません。
実行結果↓
ポテパンとジェネリクスを学ぶ 300
ジェネリクス複数の型パラメータ
ジェネリクスは、カンマ区切りで複数の型パラメータの使用も可能です。
サンプルコードを記述します。
class GenericSample<V, N>{ private V v; private N n; public GenericSample(V v, N n){ this.v = v; this.n = n; } public V getV(){ return v; } public N getN(){ return n; } } public class Main { public static void main(String[] args) { GenericSample<String, Integer> sample = new GenericSample<>("ポテパン", 500); String str = sample.getV(); Number num = sample.getN(); System.out.println(str); System.out.println(num); } }
実行結果↓
ポテパン 500
まとめ
Javaのジェネリクスについて意味や使い方を紹介しました。
ジェネリクスを利用すれば、さまざまなデータ型で似たような処理を動かしたい場合にそれぞれ処理を記述しなくて済みます。
また、 コードの記述ミスが見つかりやすくなったり、何の型を扱っているかわかりやすいといったメリットもあります。
ジェネリクスを活用する際は、ぜひこの記事を参考にしてください!
ジェネリクスを使うと、
1. コードの記述ミスが見つかりやすい
2.コード上で何の型を扱っているかわかりやすい
といったメリットがあります。