我只是想了解extends
Java 泛型中的关键字。
List<? extends Animal>
意味着我们可以将任何对象塞入List
which IS A Animal
那么以下内容是否也意味着同样的事情:
List<Animal>
有人可以帮我知道上述两者之间的区别吗?对我来说extends
,这里听起来是多余的。
谢谢!
我只是想了解extends
Java 泛型中的关键字。
List<? extends Animal>
意味着我们可以将任何对象塞入List
which IS A Animal
那么以下内容是否也意味着同样的事情:
List<Animal>
有人可以帮我知道上述两者之间的区别吗?对我来说extends
,这里听起来是多余的。
谢谢!
List<Dog>
是 的子类型List<? extends Animal>
,但不是 的子类型List<Animal>
。
为什么List<Dog>
不是 的子类型List<Animal>
?考虑以下示例:
void mySub(List<Animal> myList) {
myList.add(new Cat());
}
如果允许您将 a 传递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
}
您可以将 a 传递List<Dog>
给此函数,但编译器意识到向列表中添加某些内容可能会给您带来麻烦。如果您使用super
而不是extends
(允许您传递 a 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
}
这背后的理论是协变和逆变。
有了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 可以持有的对象类型的声明。这是关于 L 可以引用哪些类型的列表的声明。
例如,我们可以这样做:
L = aL; // remember aL is a List of Animals
但是现在编译器对 L 的所有了解是它是一个[Animal 或 Animal 的子类型] 的列表
所以现在以下内容无效:
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
在上面,我们尝试将 Animal 转换为 Goat。那是行不通的,因为如果在这样做之后我们试图让那个动物像山羊一样做一个“头撞”怎么办?我们不一定知道 Animal 可以做到这一点。
它不是。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>
,它也可以是一个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>
,无论有没有? extends
东西。