web-dev-qa-db-ja.com

ジェネリックス:リスト<?動物>はリスト<動物>と同じですか?

Java Generics。のextendsキーワードを理解しようとしています。

List<? extends Animal>は、Listに任意のオブジェクトを格納できることを意味しますIS AAnimal

次に、以下も同じことを意味しません:

List<Animal>

上記の2つの違いを誰かに教えてもらえますか?私にはextendsだけ冗長に聞こえます。

ありがとう!

47
peakit

List<Dog>List<? extends Animal>のサブタイプですが、List<Animal>のサブタイプではありません。

List<Dog>List<Animal>のサブタイプではないのはなぜですか?次の例について考えてみます。

void mySub(List<Animal> myList) {
    myList.add(new Cat());
}

この関数にList<Dog>を渡すことが許可されている場合、ランタイムエラーが発生します。


編集:ここで、代わりにList<? extends Animal>を使用すると、次のようになります。

void mySub(List<? extends Animal> myList) {
    myList.add(new Cat());     // compile error here
    Animal a = myList.get(0);  // works fine 
}

あなたはcouldにこの関数にList<Dog>を渡しますが、コンパイラはリストに何かを追加すると問題が発生する可能性があることを認識しています。 superの代わりにextendsを使用する場合(List<LifeForm>を渡すことができます)、それは逆です。

void mySub(List<? super Animal> myList) {
    myList.add(new Cat());     // works fine
    Animal a = myList.get(0);  // compile error here, since the list entry could be a Plant
}

この背後にある理論は Co- and Contravariance です。

71
Heinzi

あなたはすでに回答を受け入れているようですが、私はここでいくつかの助けになると思うので、私の見解を追加したいと思います。

List<Animal>List<? extends Animal>の違いは次のとおりです。


List<Animal>を使用すると、あなたが持っているものは間違いなく動物のリストです。それらのすべてが実際に「動物」である必要はありません-それらは派生型であることもできます。たとえば、動物のリストがある場合、カップルがヤギであり、その一部が猫などであることは理にかなっています。

たとえば、これは完全に有効です。

List<Animal> aL= new List<Animal>();
aL.add(new Goat());
aL.add(new Cat());
Animal a = aL.peek();
a.walk(); // assuming walk is a method within Animal

もちろん、以下は有効ではありません

aL.peek().meow(); // we can't do this, as it's not guaranteed that aL.peek() will be a Cat

List<? extends Animal>を使用すると、処理しているリストのタイプについてステートメントを作成していることになります。

例えば:

List<? extends Animal> L;

これは実際にはnotではなく、オブジェクトLのタイプの宣言はholdできます。 Lが参照できるリストの種類に関する記述です。

たとえば、次のようにできます。

L = aL; // remember aL is a List of Animals

しかし、コンパイラがLについて知っているのは、それが[= /// =)の[動物または動物のサブタイプ]のリストであることです

そのため、以下は無効です。

L.add(new Animal()); // throws a compiletime error

私たちが知っているすべてのことから、Lは山羊のリストを参照している可能性があるため、動物を追加することはできません。

理由は次のとおりです。

List<Goat> gL = new List<Goat>(); // fine
gL.add(new Goat()); // fine
gL.add(new Animal()); // compiletime error

上記では、動物を山羊にキャストしようとしています。それがうまくいかないのは、それを行った後で、その動物に山羊のように「頭突き」をさせようとした場合はどうなるでしょうか。動物がそれを行うことができることは必ずしもわかりません。

42
Cam

そうではない。 List<Animal>は、この変数に割り当てられる値は「タイプ」List<Animal>でなければならないことを示しています。ただし、これはAnimalオブジェクトのみが存在しなければならないという意味ではなく、サブクラスも存在する可能性があります。

List<Number> l = new ArrayList<Number>();
l.add(4); // autoboxing to Integer
l.add(6.7); // autoboxing to Double

Numberオブジェクトを取得したリストに関心がある場合は、List<? extends Number>構文を使用しますが、Listオブジェクト自体はList<Number>型である必要はなく、サブクラスのその他のリスト(List<Integer>など)を使用できます。

これは、メソッドの引数でNumbersのリストが必要ですが、List<Number>でもかまいません。List<Double>でもかまいません」。これにより、いくつかのサブクラスのリストがある場合に、奇妙なダウンキャストを回避できますが、メソッドはベースクラスのリストを想定しています。

public void doSomethingWith(List<Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // not working

これは、List<Number>ではなく、List<Double>を期待しているようには機能しません。ただし、List<? extends Number>を作成した場合は、List<Double>オブジェクトではない場合でも、List<Number>オブジェクトを渡すことができます。

public void doSomethingWith(List<? extends Number> l) {
    ...
}

List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // works

注:この全体は、リスト自体のオブジェクトの継承とは無関係です。 List<Number>の有無にかかわらず、? extendsリストにDoubleおよびIntegerオブジェクトを追加できます。

17
Progman