泛型:列表<?extends Animal>和List <Animal>相同?

pea*_*kit 45 java generics extends covariance contravariance

我只是想了解extendsJava Generics中的关键字.

List<? extends Animal>意味着我们可以在任何东西对象List是阿 Animal

那么以下也不会意味着同样的事情:

List<Animal>
Run Code Online (Sandbox Code Playgroud)

有人能帮助我了解上述两者之间的区别吗?对我来说extends这里听起来多余.

谢谢!

Hei*_*nzi 73

List<Dog>是子类型List<? extends Animal>,但不是子类型List<Animal>.

为什么List<Dog>不是子类型List<Animal>?请考虑以下示例:

void mySub(List<Animal> myList) {
    myList.add(new Cat());
}
Run Code Online (Sandbox Code Playgroud)

如果允许您传递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 
}
Run Code Online (Sandbox Code Playgroud)

可以传递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
}
Run Code Online (Sandbox Code Playgroud)

这背后的理论是协变和反演.


Cam*_*Cam 43

我知道你已经接受了答案,但我想加上我的看法,因为我觉得我可以在这里提供一些帮助.

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
Run Code Online (Sandbox Code Playgroud)

当然,下面会不会是有效的:

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

有了List<? extends Animal>,您正在声明您正在处理的列表类型.

例如:

List<? extends Animal> L;
Run Code Online (Sandbox Code Playgroud)

这实际上不是 L可以容纳的对象类型的声明.这是关于L可以引用什么类型的列表的声明.

例如,我们可以这样做:

L = aL; // remember aL is a List of Animals
Run Code Online (Sandbox Code Playgroud)

但是现在所有编译器都知道L是它是[动物或动物的子类型]的列表

所以现在以下内容无效:

L.add(new Animal()); // throws a compiletime error
Run Code Online (Sandbox Code Playgroud)

因为据我们所知,L可以引用山羊列表 - 我们无法添加动物.

原因如下:

List<Goat> gL = new List<Goat>(); // fine
gL.add(new Goat()); // fine
gL.add(new Animal()); // compiletime error
Run Code Online (Sandbox Code Playgroud)

在上面,我们试图将动物作为山羊.这不起作用,因为如果在做完之后我们试图让那个动物做一个"头颅",像山羊一样?我们不一定知道动物可以做到这一点.

  • +"这实际上不是L可以容纳的对象类型的声明.它是关于L可以引用哪种列表的声明." (5认同)

Pro*_*man 17

它不是.List<Animal>表示分配给此变量的值必须为"type" List<Animal>.然而,这并不意味着必须只有Animal对象,也可以有子类.

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

List<? extends Number>如果您对获得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
Run Code Online (Sandbox Code Playgroud)

这不是你期望的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
Run Code Online (Sandbox Code Playgroud)

注意:这整个内容与列表本身中对象的继承无关.你仍然可以在列表中添加Double和添加Integer对象List<Number>,有或没有? extends东西.