我正在研究Oracle文档中的通配符,我无法理解以下部分:
*考虑编写一个打印出集合中所有元素的例程的问题。以下是用该语言的旧版本(即 5.0 之前的版本)编写它的方法: *
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
*这是使用泛型(和新的 for 循环语法)编写它的天真的尝试: *
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
*问题是这个新版本的用处远不如旧版本。虽然旧代码可以使用任何类型的集合作为参数来调用,但新代码只接受 Collection,正如我们刚刚演示的那样,它不是所有类型集合的超类型! *
https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html
教程说
Collection<Object>
不是所有类型集合的超类型。例如,我很好奇为什么 ArrayList<String>
不是 Collection<Object>
的子类型?另外,为什么 ArrayList<String>
不是 ArrayList<Object>
的子类型?
根据我的理解,所有类都是 Object 类的后代。接口和原始类型可能是一个例外。
因为宇宙不是这样运作的。
这是一些从根本上有问题的代码:
List<Integer> integers = new ArrayList<Integer>();
List<Number> numbers = integers;
Number n = 5.5; // a double.
numbers.add(n);
Integer firstInt = integers.get(0);
经历它。请记住,java 是基于引用的,并且该片段中只有一个
new
,因此只有一个列表。该 add
呼叫将该号码添加到一个列表中。那么,你告诉我,这个应该在哪里爆炸呢?因为它必须在某个地方爆炸。
如果它按照您认为应该的方式工作,那么我猜它会在最后一行爆炸,其中
integers.get(0)
返回类型为 Double
的对象,它不是整数。但这并不完全正确 - 真正的爆炸应该发生在 add
线上。请记住, integers
和 numbers
指向完全相同的列表,因此,这正是非整数出现在 List<Integer>
中的时刻,因此,不应允许该行。
但是编译器如何找出问题所在呢? (我们希望这是一个编译器错误 - 这就是类型显式类型的全部点 - 我们在编写问题时发现问题,而不是在运行代码时发现问题!)
我们有一个
List<Number>
,我们在其上调用 .add()
,传递 Number
类型的表达式。编译器不可能认为 that 是问题行。
因此,真正发生的是正确的调用:泛型是不变的,这意味着,在
<>
中,只有 exact 类型有效。类型为 List<Number>
的变量只能在其 Object
内分配 exactly
<>
类型的值。没有别的。具体来说,不是它的子类型。
是的,在“普通java”中(
<>
之外)java是协变的——允许使用子类型。 Number n = someInteger;
还好。 List<Number> listOfN = someListOfIntegers;
不太好。
泛型允许您明确选择协变甚至逆变。以下是它的工作原理,使用相同的片段:
List<Integer> integers = new ArrayList<Integer>();
List<? extends Number> numbers = integers;
Number n = 5.5; // a double.
numbers.add(n);
Integer firstInt = integers.get(0);
注意
? extends Number
选择协方差,而 ?
最好读作“我不知道”。例如,您可能会想将 List<? extends Number>
读作“包含扩展 Number 的内容的列表”,但这是错误的 - List<Number>
已经是“包含扩展 Number 的内容的列表”,那么 ? extends
就会没用。阅读 List<? extends Number>
的正确方法是:“一个列表包含......我不知道。我所知道的是,无论这个列表包含什么类型......它要么是 Number 要么是 Number 的某些子类型”。你所做的任何事情都必须适用于任何可能发生的事情。
因此,现在代码片段does在
add
行失败了。事实上,给定一个 List<? extends Whatever>
(记住 List<?>
只是 List<? extends Object>
的语法糖,所以这才算),你不能 add
任何东西,因为要添加一些东西,它必须适用于任何可能的选择。因此,鉴于 Integer
是适合“我不知道,我所知道的是,它是 Number 的某种子类型”的事物,而 Double
是另一件事也适合,那么什么表达式是 both Integer
和Double
?没有——那是不可能的。除了字面上的 list.add(null)
的学术案例(这是这里唯一允许的),这之所以有效,只是因为 null
作为一个表达式神奇地同时是所有类型。
你想要的是
printCollection(Collection<?> c)
。
正确地阅读它:“该参数需要一个集合..我不知道。”。但是,无论做出什么选择,如果将
thatCollection.get(0)
分配给 Object
,那就没问题了 - 因为我们确实知道集合必须保存对象(它们不能保存基元;它们会被自动装箱为对象)。您的代码适用于“所有”可能的选项,因此编译器允许它。您将无法在此集合上调用 .add
,但这没关系 - 您不会调用 add
。如果你尝试过,编译器会正确地阻止你。