Java ジェネリクスは、ジェネリクス導入前のバイトコードとの互換性を保ちながら、
コンパイル時の型チェックを提供します。コンパイラーは型引数を消去し、必要な場所に
キャストを挿入し、消去後もオーバーライドを維持するためブリッジメソッドを生成する
ことがあります。その結果、List<String> と List<Integer> は実行時に同じクラスを
持ち、オブジェクトからジェネリック型引数を確実に復元することはできません。
不変性と境界 #
ジェネリッククラスは不変です。List<Integer> は List<Number> ではありません。
後者を許すと Double を挿入できてしまうためです。ワイルドカードは API 境界で
変性を表現します。
static double sum(List<? extends Number> values) {
return values.stream().mapToDouble(Number::doubleValue).sum();
}
static void addDefaults(List<? super Integer> destination) {
destination.add(0);
}呼び出し側が読み取る producer には extends を、呼び出し側が書き込む consumer
には super を使用します。List<? extends Number> は Number 値を生成できますが、
null 以外の値を受け付けません。List<? super Integer> は Integer 値を受け付けますが、
生成できるのは Object だけです。「読み取り専用」と「書き込み専用」は簡略表現で
あって能力規則ではありません。どちらのリストも、実装が許せば照会、クリア、null
の受け取りが可能です。
キャプチャと推論 #
各ワイルドカードは、未知のキャプチャ型を表します。ワイルドカードコンテナーでは、 独立した 2 つのワイルドカード値が同じ型であることを表現できません。その関係が 重要な場合は、ジェネリックヘルパーを使用します。
static void reverse(List<?> list) { reverseCaptured(list); }
private static <T> void reverseCaptured(List<T> list) {
Collections.reverse(list);
}メソッド推論は、引数の制約、ターゲット型、宣言された境界を組み合わせます。
ダイヤモンド演算子はコンストラクター呼び出しの型引数を推論しますが、raw 型を安全に
するものではありません。境界によって能力を要求できます。
<T extends Comparable<? super T>> T max(List<? extends T> values).
交差型境界には、1 つのクラスに続けてインターフェイスを含められます。
型消去下での構築 #
new T()、new T[n]、T.class は使用できません。型消去では、実行時に必要な
コンストラクター、コンポーネント型、クラスリテラルが保持されないためです。代わりに
ファクトリを渡します。
static <T> T create(Supplier<? extends T> factory) {
return factory.get();
}リフレクション統合では Class<T> を受け取り、検査対象のリフレクション例外を処理
します。ただし、呼び出し側が構築を担当する場合は Supplier<T> を優先します。
ジェネリック配列には引き続き制限があります。配列は具象化され共変である一方、
ジェネリック型は消去され不変だからです。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)