【问题标题】:Can I use generics wildcard in List declaration?我可以在 List 声明中使用泛型通配符吗?
【发布时间】:2014-02-05 10:31:35
【问题描述】:

考虑以下代码:

class Super {}
class Sub extends Super {}
class Test {
    public static void main(String[] args) {
        List<? extends Super> list = new ArrayList<Sub>(); //1
        list.add(new Sub()); //2
    }
}

第一行编译成功,第二行编译失败:

The method add(capture#2-of ? extends Super) in the type List<capture#2-of ? extends Super> is not applicable for the arguments (Sub)

我的问题是:
1)为什么第1行编译成功?
2) 第 1 行是声明列表(或其他集合)的好习惯吗?
3) 为什么在第 1 行声明 list 为 Sub 类型后,第 2 行编译失败?
4) Eclipse 的自动完成功能说现在列表中只允许“空”元素。为什么?

非常感谢!

【问题讨论】:

    标签: java generics arraylist wildcard


    【解决方案1】:

    1) 为什么第一行编译成功?

    第一行编译是因为List&lt;Sub&gt;List&lt;? extends Super&gt; 的子类,并且只有在 List 不允许您向其中添加任何新成员时才会如此。

    ? 表示您并不完全知道它是List&lt;Sub&gt;List&lt;Sub1&gt;,因此允许将新元素添加到列表中是不安全的,因此不允许添加。

    2) 第 1 行是声明列表(或其他 收藏)?

    如果您已经知道它将是List&lt;Sub&gt;,那么我没有发现任何用处,但是当您将列表传递给其他类(如实用程序)时,通配符会被大量使用。

    3) 为什么第 2 行编译失败,list 被声明为 在第 1 行输入 Sub?

    因为正如我已经解释过的,当您不知道确切类型时,将任何元素添加到列表中是不安全的。

    4) Eclipse 的自动补全功能说只允许“空”元素 现在列出。为什么?

    因为null 是所有引用类型,这就是为什么您可以为任何对象分配null 的值。

    在使用泛型时始终记住PECS (Producer Extends Consumer Super) rule by Josh Bloch

    良好的参考:

    【讨论】:

    • 好吧..所以根据我的理解,如果使用泛型,那么集合中只允许使用一种特定类型。因此,如果 > 在列表声明中使用,那么为了保持元素类型的单一和安全,编译器必须将元素类型限制为 null (因为直到运行时它才知道 ? 是什么)。我说的对吗?
    • 差不多。您可以使用List&lt;Super&gt; 来保留Super 的多个子类型的元素。但是您将无法将其初始化为ArrayList&lt;Sub&gt;。然后你必须将它实例化为List&lt;Super&gt; list = new ArrayList&lt;Super&gt;()。并且null 是允许的,因为空值属于所有引用类型。
    • 感谢您的参考 - PECS 规则真的很有帮助!
    • 是的,它是由 Joshua Bloch 在他的《Effective Java》一书中给出的。这是一本了不起的书。也通过它。很高兴能提供帮助。
    【解决方案2】:

    在第 1 行中捕获声明在方法参数中是很好的。见Collection.addAll(Collection&lt;? extends E&gt;)。如果您需要扩展 Super 的列表,只需使用 List&lt;Super&gt;

    【讨论】:

      【解决方案3】:

      您已将list 声明为,它可以分配给Super 类的任何子类型。因此,分配一个Sub 类型列表是可以的,编译器允许编译。但这并不意味着,您可以在其中添加特定的 Object 类型。

      &lt;? extends Super&gt; 不代表,你可以添加任何 Super 的子类型。这意味着,您可以为其分配任何子类型集合。

      【讨论】:

        【解决方案4】:

        1) 为什么第一行编译成功?

        您将list 声明为“从Super 派生的事物的列表”。您为其分配了Sub 的列表。 Sub 是“派生自 Super 的东西”。

        2) 第 1 行是声明列表(或其他集合)的好习惯吗?

        没有。通配符用于函数参数。局部变量在其泛型参数中应尽可能具体,以避免出现您所面临的问题。

        3) 为什么在第 1 行声明 list 为 Sub 类型后,第 2 行编译失败?

        谬误。 list 被声明为具有“源自Super 的东西”的元素,而不是您声称的Sub。而且您不能将Sub 添加到列表中,因为“某事”可能不是Sub;可能是Sub2,添加就相当于这个非法赋值:

        class Super {}
        class Sub extends Super {}
        class Sub2 extends Super {}
        Sub2 s = new Sub();
        

        这里的主要误解似乎是您认为通配符在分配时会以某种方式被替换。它不是。它仍然是一个通配符,并且只进行兼容性检查。

        4) Eclipse 的自动完成功能说现在列表中只允许“空”元素。为什么?

        null 是唯一可以是任何可能引用类型的值,因此无论通配符代表什么,它都与列表兼容。

        【讨论】:

          【解决方案5】:

          1) 为什么第一行编译成功?

          您基本上定义列表以包含扩展(或为)Super 的任何类型的元素,即编译器知道该列表中的每个元素至少应具有 Super 的属性。

          由于SubSuper 的子类,因此任何仅包含Sub 元素的列表也满足所有元素都是Super 实例的要求,List&lt;? extends Super&gt; list = new ArrayList&lt;Sub&gt;(); 是正确的。

          2) 第 1 行是声明列表(或其他集合)的好习惯吗?

          作为一个取决于个人风格的局部变量,恕我直言。当以这种方式声明参数(或实例/静态变量)时,这通常不仅是好的风格,而且也是必需的。

          考虑一种迭代数字集合并返回总和的方法。 您可以将参数声明为Collection&lt;Number&gt;,但是如果没有讨厌的演员表,您将无法传递Collection&lt;Integer&gt;。如果参数声明为Collection&lt;? extends Number&gt;,则可以传递Collection&lt;Integer&gt;

          3) 为什么在第 1 行声明 list 为 Sub 类型后,第 2 行编译失败?

          原因是编译器不知道列表中元素的确切类型。是Super的列表还是Sub的列​​表?

          List&lt;? extends Number&gt; list 为例。您不知道您是否拥有List&lt;Number&gt;List&lt;Double&gt;List&lt;Integer&gt;,因此无法判断list.add( new Integer(1) ); 是否可以。编译器就是这么看的。

          4) Eclipse 的自动完成功能说现在列表中只允许“空”元素。为什么?

          我不得不在这里猜测,但是将 null 添加到列表中是可以的,因为无论实际列表声明什么类型,您始终可以将 null 转换为该类型。

          【讨论】:

            猜你喜欢
            • 2012-12-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-06
            • 1970-01-01
            • 2017-10-09
            相关资源
            最近更新 更多