带有通配符的列表会导致Generic voodoo错误

时间:2011-03-23 15:52:43

标签: java generics generic-list generic-collections

有谁知道为什么以下代码无法编译? 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);

5 个答案:

答案 0 :(得分:50)

(我在此假设BarBaz都是Foo的子类型。)

List<? extends Foo>表示某种类型的元素列表,它是Foo的子类型,但我们不知道哪种类型。此类列表的示例包括ArrayList<Foo>LinkedList<Bar>ArrayList<Baz>

由于我们不知道哪个子类型是类型参数,因此我们无法将Foo个对象放入其中,BarBaz个对象。但我们仍然知道type参数是Foo的子类型,因此列表中已有的每个元素(以及我们可以从列表中获取的)都必须是Foo对象,因此我们可以使用{ {1}}和类似的事情。

这样的列表只能用于从列表中取出元素,而不是添加元素(除了Foo f = list.get(0);,但我不知道编译器是否实际允许这样做。)

另一方面,null允许添加List<Foo>对象的任何对象 - FooBarBaz的子类型,所有FooBar个对象是Baz个对象,因此也可以添加它们。

答案 1 :(得分:24)

记住PECS:Producer Extends, Consumer Super

由于您尝试向list2添加项目,因此它是使用者,无法声明为List<? extends Foo>。但是当你将list2添加到list1时,你也将list2用作生产者。因此,list2既是生产者又是消费者,必须是List<Foo>

list1,作为纯消费者,可以是List<? super Foo>

答案 2 :(得分:8)

他们是错误的。考虑到BarBaz是扩展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的子类。