有谁知道为什么以下代码无法编译? add()和addAll()都不能按预期工作。删除“?extends”部分会使一切正常,但之后我将无法添加Foo的子类。
List<? extends Foo> list1 = new ArrayList<Foo>();
List<? extends Foo> list2 = new ArrayList<Foo>();
/* Won't compile */
list2.add( new Foo() ); //error 1
list1.addAll(list2); //error 2
错误1:
IntelliJ说:
add(capture<? extends Foo>) in List cannot be applied to add(Foo)
编译器说:
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
错误2:
IntelliJ给了我
addAll(java.util.Collection<? extends capture<? extends Foo>>) in List cannot be applied to addAll(java.util.List<capture<? extends Foo>>)
编译器只是说
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
list1.addAll(list2);
答案 0 :(得分:50)
(我在此假设Bar
和Baz
都是Foo
的子类型。)
List<? extends Foo>
表示某种类型的元素列表,它是Foo的子类型,但我们不知道哪种类型。此类列表的示例包括ArrayList<Foo>
,LinkedList<Bar>
和ArrayList<Baz>
。
由于我们不知道哪个子类型是类型参数,因此我们无法将Foo
个对象放入其中,Bar
或Baz
个对象。但我们仍然知道type参数是Foo
的子类型,因此列表中已有的每个元素(以及我们可以从列表中获取的)都必须是Foo
对象,因此我们可以使用{ {1}}和类似的事情。
这样的列表只能用于从列表中取出元素,而不是添加元素(除了Foo f = list.get(0);
,但我不知道编译器是否实际允许这样做。)
另一方面,null
允许添加List<Foo>
对象的任何对象 - Foo
和Bar
是Baz
的子类型,所有Foo
和Bar
个对象是Baz
个对象,因此也可以添加它们。
答案 1 :(得分:24)
记住PECS:Producer Extends, Consumer Super。
由于您尝试向list2添加项目,因此它是使用者,无法声明为List<? extends Foo>
。但是当你将list2添加到list1时,你也将list2用作生产者。因此,list2既是生产者又是消费者,必须是List<Foo>
。
list1,作为纯消费者,可以是List<? super Foo>
。
答案 2 :(得分:8)
他们是错误的。考虑到Bar
和Baz
是扩展Foo
的两种不同类型,请让我们修改您的代码:
List<? extends Foo> list1 = new ArrayList<Bar>();
List<? extends Foo> list2 = new ArrayList<Baz>();
如果允许list1.add(new Foo())
,则可以在包含Bar实例的集合中添加Foo实例。这解释了第一个错误。
如果允许list1.addAll(list2)
,则list2中的所有Baz实例都将添加到list1,其中只包含Bar实例。这解释了第二个错误。
答案 3 :(得分:4)
让我试着解释一下你可能需要使用<? extend Classname>
。
所以,假设你有2个课程:
class Grand {
private String name;
public Grand(String name) {
this.setName(name);
}
public Grand() {
}
public void setName(String name) {
this.name = name;
}
}
class Dad extends Grand {
public Dad(String name) {
this.setName(name);
}
public Dad() {
}
}
并且假设您有2个集合,每个集合包含一些Grands和一些Dads:
List<Dad> dads = new ArrayList<>();
dads.add(new Dad("Dad 1"));
dads.add(new Dad("Dad 2"));
dads.add(new Dad("Dad 3"));
List<Dad> grands = new ArrayList<>();
dads.add(new Dad("Grandpa 1"));
dads.add(new Dad("Grandpa 2"));
dads.add(new Dad("Grandpa 3"));
现在,让我们想要拥有集合,它将包含Grand或Dad对象:
List<Grand> resultList;
resultList = dads; // Error - Incompatable types List<Grand> List<Dad>
resultList = grands;//Works fine
我们如何避免这种情况?只需使用通配符:
List<? extends Grand> resultList;
resultList = dads; // Works fine
resultList = grands;//Works fine
请注意,您无法在此类(resultList)集合中添加新项目。有关更多信息,您可以阅读Java中的Wildcard和PECS概念
答案 4 :(得分:2)
对不起,也许我误解了你的问题,但是说:
public class Bar extends Foo{ }
此代码:
List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );
不要为我生成任何错误。
因此,删除通配符允许添加Foo的子类。