Java ジェネリックスの説明

巴扎黑
リリース: 2017-04-30 10:03:59
オリジナル
1700 人が閲覧しました

はじめに

ジェネリックは Java の非常に重要な知識ポイントであり、Java コレクション クラス フレームワークで広く使用されています。この記事では、Java ジェネリックの設計をゼロから見ていきます。これには、ワイルドカード処理と煩わしい型の消去が含まれます。

一般的な基本

汎用クラス

最初に単純な Box クラスを定義します:

public class Box {
    private String object;
    public void set(String object) { this.object = object; }
    public String get() { return object; }
}
ログイン後にコピー

これは最も一般的なアプローチです。この方法の欠点は、今後 Integer などの他のタイプの要素をロードする必要がある場合、コードを書き直す必要があることです。再利用に関しては、ジェネリックを使用するとこの問題をうまく解決できます。

りー

このようにして、Box クラスを再利用でき、T を任意の型に置き換えることができます:

public class Box<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
ログイン後にコピー

一般的なメソッド

ジェネリック クラスについて読んだ後、ジェネリック メソッドを見てみましょう。ジェネリック メソッドの宣言は非常に簡単で、戻り値の型の前に のような形式を追加するだけです。 次のような汎用メソッドを呼び出すことができます:

Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();
ログイン後にコピー

または、Java1.7/1.8 で型推論を使用して、Java が対応する型パラメータを自動的に導出できるようにします:

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}
public class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
ログイン後にコピー

境界記号

ここで、汎用配列内の特定の要素より大きい要素の数を見つける関数を実装したいと思います。これは次のように実装できます。 しかし、これは明らかに間違っています。short、int、double、long、float、byte、char などのプリミティブ型を除き、他のクラスは演算子 > を使用できない可能性があるため、コンパイラはエラーを報告します。この問題を解決するにはどうすればよいですか?答えは、境界文字を使用することです。

りー

次のようなステートメントを作成することは、型パラメーター T が Comparable インターフェイスを実装するクラスを表すことをコンパイラーに伝えることと同じであり、すべてのクラスが少なくとも CompareTo メソッドを実装することをコンパイラーに伝えることと同じです。

りー

ワイルドカード

ワイルドカードを理解する前に、まず概念を明確にする必要があります。上で定義した Box クラスを次のようなメソッドに追加するとします。 それでは、Box n は現在どのような種類のパラメータを受け入れることができるのでしょうか? Box または Box を渡すことはできますか?答えは「ノー」です。Integer と Double は Number のサブクラスですが、ジェネリックスでは Box と Box の間には関係がありません。これは非常に重要です。完全な例を通して理解を深めましょう。

まず、以下で使用するいくつかの単純なクラスを定義します。 次の例では、ジェネリック クラス Reader を作成し、f1() で Fruit f = FruitReader.readExact(apples); を実行すると、List と List< の間にギャップがあるため、コンパイラはエラーを報告します。 Apple>関係ありません。

りー

しかし、私たちの通常の思考習慣によれば、Apple と Fruit の間には関連性があるはずですが、コンパイラーはそれを認識できません。では、この問題を汎用コードで解決するにはどうすればよいでしょうか。この問題はワイルドカードを使用することで解決できます:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
ログイン後にコピー

これは、fruitReader の readCovariant メソッドによって受け入れられるパラメーターは Fruit のサブクラス (Fruit 自体を含む) のみである必要があるため、サブクラスと親クラスの間の関係も関連付けられることをコンパイラーに伝えるのと同じです。

PECS原則

上で と同様の使用法を示しましたが、これを使用してリストから要素を取得できるので、リストに要素を追加できますか?試してみましょう:

rreee

答えは「いいえ」です。Java コンパイラではこれを行うことができません。なぜでしょうか?この問題をコンパイラの観点から検討することもできます。 List を拡張したものなので、それ自体が複数の意味を持つ可能性があります: Apple を追加しようとすると、flist が新しい ArrayList();

を指す場合があります。 Orange を追加しようとすると、flist が新しい ArrayList();