Collection<Object>怎么不是所有类型集合的超类型?

问题描述 投票:0回答:1

我正在研究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 类的后代。接口和原始类型可能是一个例外。

java generics collections wildcard
1个回答
0
投票

因为宇宙不是这样运作的。

这是一些从根本上有问题的代码:

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
。如果你尝试过,编译器会正确地阻止你。
    

© www.soinside.com 2019 - 2024. All rights reserved.