Java Generics

最終更新: 2025-02-02 05:23:41 +0800 +0800
tags: Generics | Java
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

Java ジェネリクスは、ジェネリクス導入前のバイトコードとの互換性を保ちながら、 コンパイル時の型チェックを提供します。コンパイラーは型引数を消去し、必要な場所に キャストを挿入し、消去後もオーバーライドを維持するためブリッジメソッドを生成する ことがあります。その結果、List<String> と List<Integer> は実行時に同じクラスを 持ち、オブジェクトからジェネリック型引数を確実に復元することはできません。

不変性と境界 #

ジェネリッククラスは不変です。List<Integer> は List<Number> ではありません。 後者を許すと Double を挿入できてしまうためです。ワイルドカードは API 境界で 変性を表現します。

java
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 つのワイルドカード値が同じ型であることを表現できません。その関係が 重要な場合は、ジェネリックヘルパーを使用します。

java
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 は使用できません。型消去では、実行時に必要な コンストラクター、コンポーネント型、クラスリテラルが保持されないためです。代わりに ファクトリを渡します。

java
static <T> T create(Supplier<? extends T> factory) {
    return factory.get();
}

リフレクション統合では Class<T> を受け取り、検査対象のリフレクション例外を処理 します。ただし、呼び出し側が構築を担当する場合は Supplier<T> を優先します。 ジェネリック配列には引き続き制限があります。配列は具象化され共変である一方、 ジェネリック型は消去され不変だからです。

References #

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)