请考虑以下代码:
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”元素。为什么?
非常感谢!
答案 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>
由于Sub
是Super
的子类,因此任何仅包含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> list
,List<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
是任何可能的引用类型的唯一值,因此无论通配符代表什么,它都与列表兼容。