【问题标题】:Lists with wildcards cause Generic voodoo error带有通配符的列表会导致通用巫毒教错误
【发布时间】:2011-03-23 15:52:43
【问题描述】:

有谁知道为什么下面的代码不能编译? 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);

【问题讨论】:

  • 请解释你为什么使用,而只是 Foo。在解释这一点时,也许事情会变得清楚。
  • 嘿,英戈。很好的观点,并且可能更早地创造了更好/更准确的答案。我对使用通配符接受子类型的方法参数感到困惑,并认为我需要使用 符号以便能够将子类型添加到集合中。 Myers 和 Paulo(下)帮助了解了为什么没有必要(或正确)。很好的评论 - 谢谢!

标签: java generics generic-list generic-collections


【解决方案1】:

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

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

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

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

另一方面,List&lt;Foo&gt; 允许添加任何属于 Foo 对象的对象 - 由于 BarBazFoo 的子类型,所有 BarBaz 对象都是 @ 987654342@ 个对象,因此也可以添加它们。

【讨论】:

  • 谢谢,保罗!迈克尔迈尔斯的链接答案也很中肯,但你的答案不需要绕道:)
【解决方案2】:

记住 PECS:Producer Extends, Consumer Super

由于您试图将项目添加到 list2,它是一个消费者,不能声明为 List&lt;? extends Foo&gt;。但是,当您将 list2 添加到 list1 时,您也将它用作生产者。所以list2既是生产者又是消费者,必须是List&lt;Foo&gt;

list1,作为纯消费者,可以是List&lt;? super Foo&gt;

【讨论】:

  • 感谢您的帮助 - 链接页面上的 PECS 答案非常好!为您的链接答案投票,但授予保罗接受答案的功劳,因为它不需要绕道而行:)
【解决方案3】:

他们是错误的。考虑到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 实例都将添加到仅包含Bar 实例的list1。这解释了第二个错误。

【讨论】:

    【解决方案4】:

    让我试着解释一下在什么情况下你可能需要使用&lt;? extend Classname&gt;

    所以,假设你有 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 概念

    【讨论】:

    • 我看不出List&lt;Dad&gt; dadsList&lt;Dad&gt; grands 之间有什么区别,不是吗?
    【解决方案5】:

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

    public class Bar extends Foo{ }
    

    这段代码:

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

    不要为我产生任何错误。

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

    【讨论】:

    • 问题中使用List&lt;? extends Foo&gt;作为变量类型,而不是List&lt;Foo&gt; :-)
    • @Vivien:我知道。我说的是可以删除通配符以编译代码。
    • 它将允许添加 Foo 的子类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-21
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 2012-12-29
    • 1970-01-01
    • 2018-08-27
    相关资源
    最近更新 更多