質問の歴史:
JavaのGenericsは、安全にコレクションやメソッドの型を扱うためにJava 5で導入されましたが、以前に作成されたバイトコードとの後方互換性を保つために実装されました。これにはtype erasure(型の消去)メカニズムが適用されました。
問題:
Javaコンパイラーは厳格な型を要求しますが、実行時にJVMは型パラメータを認識せず、Java自体のコレクションライブラリを含む多くの古いクラスやライブラリは、汎用の「生」型(raw types)で動作します。後方互換性がなければ、既存の開発をサポートすることは不可能でした。
解決策:
type erasureは、パラメータ化された型(ジェネリクス)を「生」バージョンに変換するプロセスで、JVMが既存のバイトコードを変更することなく操作できるようにします。型パラメータに関するすべての情報はコンパイル時に削除され、その代わりにObject型のオブジェクトが使用されます(または、制約が指定されている場合はそれに従います)。
コードの例:
List<String> stringList = new ArrayList<>(); stringList.add("hello"); String s = stringList.get(0); // getはObjectを返すが、コンパイラーはキャストを挿入する
主な特徴:
List<String>かList<Integer>かを知ることはできません。ジェネリクスのパラメータのみでメソッドのオーバーロードを使用できますか?
いいえ。type erasureのために、コンパイラーは同じ名前のメソッドを、異なるジェネリクスのパラメータを持っていても同じものと見なします。なぜなら、パラメータは消去されるからです。たとえば,
// コンパイルエラー! void process(List<String> list) { } void process(List<Integer> list) { }
ジェネリック型の配列を作成できますか?
いいえ、直接には。type erasureはJVMが特定のジェネリック型の配列を保持することを許可しません。たとえば,
List<String>[] array = new List<String>[10]; // コンパイルエラー
生型の配列を使用することで回避することはできますが、それは安全ではありません。
List<String>[] array = (List<String>[]) new List[10];
実行時にinstanceofを通じてジェネリックの型を確認できますか?
いいえ、型パラメータに関する情報は消去されます。確認:
if (obj instanceof List<String>) { ... } // コンパイルエラー
正しくは、基本型のみを確認します:
if (obj instanceof List) { ... }
プログラマーは異なるパラメータのリストを保存するためにパラメータ化された型の配列を作成します。 結果的に、プログラムの長時間の作業の後にオブジェクトを配列から抽出する際にClassCastExceptionが発生します—ランタイムでは型チェックが保証されていません。
利点:
欠点:
配列の代わりにコレクション(たとえば、List<List<String>>)を使用し、すべての型チェックがコンパイラーに委任されます。
利点:
欠点: