我可以在List声明中使用泛型通配符吗?

时间:2014-02-05 10:31:36

标签: java generics arraylist wildcard

请考虑以下代码:

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
    }
}

第1行成功编译,但第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行是声明List(或其他集合)的好习惯吗?
3)为什么第2行在编译失败后,列表被声明为第1行的类型Sub?
4)Eclipse的自动完成说现在列表中只允许“null”元素。为什么?

非常感谢!

5 个答案:

答案 0 :(得分:4)

  

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

第一行编译是因为List<Sub>List<? extends Super>的子类,并且只有在List不允许您向其添加任何新成员时才能为true。

?表示您并不确切知道它是List<Sub>List<Sub1>,因此允许在列表中添加新元素是不安全的,因此它不会允许它。

  

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

如果您已经知道它将是List<Sub>,那么我找不到任何用处,但是当您将List传递给其他类(如Utilities)时,会使用通配符。

  

3)为什么在声明list之后第2行无法编译   在第1行中输入Sub?

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

  

4)Eclipse的自动完成说只允许使用“null”元素   现在列出。为什么呢?

由于null属于每种引用类型,因此您可以为任何对象分配值null

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

好的参考文献:

答案 1 :(得分:3)

在第1行中捕获声明在方法参数中很好。见Collection.addAll(Collection<? extends E>)。如果您需要包含Super扩展名的列表,请使用List<Super>

答案 2 :(得分:2)

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

<? extends Super>不表示,您可以添加任何子类型的超级。这意味着,您可以为其分配任何子类型集合。

答案 3 :(得分:1)

  

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

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

由于SubSuper的子类,因此任何仅包含Sub元素的列表也符合所有元素作为Super,{{1}的实例的要求是正确的。

  

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

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

考虑一种迭代数字集合并返回总和的方法。 您可以将参数声明为List<? extends Super> list = new ArrayList<Sub>();,但是如果没有令人讨厌的演员,则无法传递Collection<Number>。如果参数声明为Collection<Integer>,您可以传递Collection<? extends Number>

  

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

原因是编译器不知道列表中元素的确切类型。它是Collection<Integer>列表还是Super列表?

Sub为例。您不知道自己是List<? extends Number> listList<Number>还是List<Double>,因此无法判断List<Integer>是否合适。这就是编译器看到它的方式。

  

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

我必须在这里猜测,但是将list.add( new Integer(1) );添加到列表中是可以的,因为无论实际列表声明什么类型,您都可以始终将null强制转换为该类型。

答案 4 :(得分:1)

  

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

您声明list是“来自Super”的内容列表。您为其分配了Sub的列表。 Sub是“源自Super”的内容。

  

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

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

  

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

谬误。 list被宣称拥有“来自Super”的元素,而不是Sub。并且您无法在列表中添加Sub,因为“某些内容”可能不是Sub;它可能是Sub2,并且添加将等同于此非法分配:

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

这里的关键误解似乎是你认为通配符在转让时会被某种方式取代。事实并非如此。它仍然是通配符,只进行兼容性检查。

  

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

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