46

有谁知道为什么以下代码无法编译?add() 和 addAll() 都不能按预期工作。删除“?扩展”部分使一切正常,但是我将无法添加 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);
4

5 回答 5

61

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

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

由于我们不知道类型参数是哪个子类型,所以我们不能将Foo对象放入其中,也Bar不能放入Baz对象。但是我们仍然知道 type 参数是 的子类型Foo,因此列表中的每个元素(并且我们可以从列表中获取)都必须是一个Foo对象,因此我们可以使用 Foo f = list.get(0);和类似的东西。

这样的列表只能用于从列表中取出元素,根本不能添加元素(除了null,但我不知道编译器是否真的允许这样做)。

List<Foo>另一方面,A允许添加任何作为对象的Foo对象 - 并且作为BarBaz的子类型Foo,所有BarBaz对象都是Foo对象,因此也可以添加它们。

于 2011-03-23T16:31:24.183 回答
29

记住 PECS:生产者扩展,消费者超级

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

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

于 2011-03-23T15:56:36.947 回答
9

他们是错误的。考虑到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 实例。这解释了第二个错误。

于 2011-03-23T15:57:49.890 回答
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 中的通配符和 PECS 概念

于 2017-02-02T12:35:31.570 回答
2

对不起,也许我误解了你的问题,但假设:

public class Bar extends Foo{ }

这段代码:

List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );

不要为我产生任何错误。

因此,删除通配符允许添加 Foo 的子类。

于 2011-03-23T15:58:28.407 回答