Java Generics。のextends
キーワードを理解しようとしています。
List<? extends Animal>
は、List
に任意のオブジェクトを格納できることを意味しますIS AAnimal
次に、以下も同じことを意味しません:
List<Animal>
上記の2つの違いを誰かに教えてもらえますか?私にはextends
だけ冗長に聞こえます。
ありがとう!
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 です。
あなたはすでに回答を受け入れているようですが、私はここでいくつかの助けになると思うので、私の見解を追加したいと思います。
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
上記では、動物を山羊にキャストしようとしています。それがうまくいかないのは、それを行った後で、その動物に山羊のように「頭突き」をさせようとした場合はどうなるでしょうか。動物がそれを行うことができることは必ずしもわかりません。
そうではない。 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
オブジェクトを追加できます。