我只是想了解Java Generics中的extends
关键字。
List<? extends Animal>
表示我们可以填充List
中的任何对象是A Animal
那么以下也不会意味着同样的事情:
List<Animal>
有人能帮助我了解上述两者之间的区别吗?对我来说extends
在这里听起来多余。
谢谢!
答案 0 :(得分:70)
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
}
您可以将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。
答案 1 :(得分:41)
我知道你已经接受了答案,但我想加上我的看法,因为我觉得我可以在这里提供一些帮助。
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;
这实际上是不 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
在上面,我们试图将动物视为山羊。这不起作用,因为如果在做完之后我们试图让那个动物做一个“头颅”,像山羊一样?我们不一定知道动物可以做到这一点。
答案 2 :(得分:16)
不是。 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
如果您对获得List<? extends Number>
个对象的列表感兴趣,则使用Number
构造,但List对象本身不需要是List<Number>
类型,但可以是任何其他子类列表(如List<Integer>
)。
这有时用于方法参数,说“我想要一个Numbers
的列表,但我不在乎它是否只是List<Number>
,它可以是{{1太“。如果你有一些子类的列表,这可以避免一些奇怪的向下转换,但是该方法需要一个基类列表。
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
列表中添加Double
和Integer
个对象,包含或不包含List<Number>
个内容。