ジェネリクスとは
ジェネリクスは、Javaの型システムを拡張する機能で、クラスやインターフェース、メソッドを一般的な形で定義することができます。これにより、コンパイル時に型の安全性を確保し、キャストを減らすことができます。
例えば、List<E>
のようなジェネリクス型を使用すると、List<String>
やList<Integer>
など、任意の型でリストを作成することができます。このE
は型パラメータと呼ばれ、具体的な型に置き換えられます。
ジェネリクスは、Javaの型安全性を向上させ、バグを早期に見つけ出すための強力なツールです。しかし、その使用は複雑であり、理解と適用には注意が必要です。特に、ワイルドカードや境界型パラメータ(extends
やsuper
)を使用すると、さらに複雑さが増します。これらの概念は、次のセクションで詳しく説明します。
Javaのジェネリクスの非変性
Javaのジェネリクスは非変性です。これは、たとえばList<String>
がList<Object>
のサブタイプでないことを意味します。つまり、ある型パラメータT
とその任意のサブタイプS
に対して、G<T>
はG<S>
のサブタイプではないということです。
これは、型の安全性を確保するための重要な特性です。例えば、List<String>
がList<Object>
のサブタイプであると仮定すると、次のようなコードが許されてしまいます。
List<String> stringList = new ArrayList<>();
List<Object> objectList = stringList; // 仮定: List<String>はList<Object>のサブタイプ
objectList.add(new Object()); // ObjectはStringのスーパータイプ
String s = stringList.get(0); // 実行時エラー
このコードは、List<String>
にObject
を追加しようとするため、実行時にエラーを引き起こします。しかし、ジェネリクスが非変性であるため、このようなコードはコンパイル時にエラーとなり、型の安全性が確保されます。
しかし、この非変性は柔軟性を制限するため、Javaではextends
キーワードを使用してジェネリクスの変性を制御することができます。これについては、次のセクションで詳しく説明します。
List<? extends T>の使用例と制限
Javaのジェネリクスでは、extends
キーワードを使用して、特定の型またはそのサブタイプのオブジェクトを受け入れることができます。これは、ジェネリクスの変性を制御するための一つの方法で、共変性と呼ばれます。
List<? extends T>
は、「TまたはTのサブタイプの要素を持つリスト」という意味になります。これは、T
の任意のサブタイプのリストを参照することができますが、リストに新しい要素を追加することはできません(nullを除く)。これは、型の安全性を確保するための制限です。
以下に、List<? extends T>
の使用例を示します。
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
List<? extends Number> numberList = integerList;
Number num = numberList.get(0); // OK
numberList.add(new Integer(3)); // コンパイルエラー
この例では、integerList
はList<Integer>
型で、これはList<? extends Number>
型のサブタイプです。そのため、numberList
にintegerList
を代入することができます。しかし、numberList.add(new Integer(3));
はコンパイルエラーになります。これは、numberList
が具体的にどのNumber
のサブタイプのリストを参照しているかが不明なため、新しい要素を追加することはできません。
このように、List<? extends T>
は読み取り専用のリストとして使用することができます。これは、ジェネリクスの非変性を補完するための重要な機能で、型の安全性を確保しながらも一定の柔軟性を提供します。しかし、その使用は複雑であり、理解と適用には注意が必要です。これについては、次のセクションで詳しく説明します。
まとめ
Javaのジェネリクスは、型の安全性を確保しながらコードの再利用性を向上させる強力な機能です。ジェネリクスは非変性であり、List<String>
はList<Object>
のサブタイプではないという特性があります。
しかし、extends
キーワードを使用することで、特定の型またはそのサブタイプのオブジェクトを受け入れるジェネリクスを作成することができます。これにより、List<? extends T>
のような形で、読み取り専用のリストを作成することが可能になります。
しかし、ジェネリクスの使用は複雑であり、特にワイルドカードや境界型パラメータを使用するときは注意が必要です。適切に使用すれば、ジェネリクスはJavaプログラミングの強力なツールとなります。この記事を通じて、その理解と適用が深まったことを願っています。