3 回答

TA贡献1804条经验 获得超7个赞
您有两种不同类型的多态性在这里以一种令人困惑的方式相互作用。
理解这一点的关键在于,除了参数多态性(即泛型)之外,还有子类型多态性,即经典的面向对象的“is-a”关系。
在 Java 中,所有对象都是Object. 所以一个可以包含Object值的容器可以包含任何值。
如果我们像刚才<Object>那样重写所有通用边界,代码的工作方式相同,显然如此:
List<Object> objectList = new ArrayList<>();
objectList.add("str1");
List<Object> numberList = objectList;
numberList.add(1);
objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
System.out.println(objectList.get(i) + "");
}
具体来说,objectList.get(i) + ""被评估为调用 的东西objectList.get(i).toString(),并且由于toString()是 的方法Object,无论 中的对象类型如何,它都将起作用objectList。
这是行不通的:
Number number = numberList.get(i); // error!
这是因为,尽管名称具有误导性,但numberList不能保证仅包含Number对象,并且实际上可能根本不包含任何Number对象!
让我们来看看为什么会这样。
首先我们创建一个对象列表:
List<? super Object> objectList = new ArrayList<>();
这种类型是什么意思?类型的List<? super Object>意思是“某种类型的对象列表,我不能告诉你是什么类型,但我知道它是什么类型,Object或者是”的超类型Object。我们已经知道这Object是子类型层次结构的根,所以这实际上与 相同List<Object>:即这个对象只能包含Object对象。
但是……这不太对。列表只能包含Object对象,但Object对象可以是任何东西!runtype 中的实际对象可以是任何类型的子类型Object(因此,除了原始类型之外的任何其他类型),但是将它们放入此列表中,您将无法再分辨它们是哪种类型的对象——他们可以是任何东西。不过,这对于该程序的其余部分所做的事情来说是可以的,因为它需要能够做的就是调用toString()对象,并且它可以做到这一点,因为它们都扩展了Object.
现在让我们看看另一个变量声明:
List<? super Number> numberList = objectList;
再次,类型List<? super Number>是什么意思?至关重要的是,它的意思是“某种类型的对象列表,我不能告诉你它是什么类型,但我知道无论它是什么类型,它都是“的一种Number或某种超类型Number”。好吧,在左边我们有一个“Number或某个超类型Number”的列表,在右边我们有一个列表Object——显然Object是一个的超类型,Number所以这个列表是一个列表Object。一切类型检查(并且,与我最初的评论相反,没有任何警告)。
所以问题就变成了:为什么 a 可以List<? super Number>包含 a String?因为 aList<? super Number>可以只是 a List<Object>,而 aList<Object>可以包含 aString因为Stringis-a Object。

TA贡献1798条经验 获得超7个赞
类型的List<? super Number>
引用可以引用List<Object>
或List<Number>
。通过此引用进行的任何操作都需要使用这些类型中的任何一种。您无法通过List<? super Number>
引用添加字符串,因为该操作仅适用于一种可能的对象类型,但您可以通过List<? super Object>
引用。
AList<? super Object>
只能引用 a List<Object>
。AList<? super Number>
可以指代 aList<Number>
或 a List<Object>
。这是一种更通用的类型,这就是允许赋值的原因。

TA贡献1797条经验 获得超6个赞
当编译器看到:
List<? super Number> numberList = objectList;
它首先捕获通配符。泛型类型变为 Y = X >Number(意味着Number的具体超类型)。所以我们有:
List<Y> numberList = objectList //with type of List<Object>;
然后编译器确定Y可以替换为Object。因此,类型是相同的,numberList并且允许指向与 相同的对象objectList。
然后将生成的字节码传递给运行时系统执行。就运行时系统而言,java.util.ArrayList由于类型擦除,两个列表都具有类型。因此,当您将字符串或其他对象放入此容器时,不会引发运行时异常。
但我也觉得有些地方不太对劲。重新表述你的问题:
编译器可以做什么来防止这种情况?
请注意,编译器不得在赋值期间抱怨,因为:
安全实例化原则:实例化具有满足参数声明约束的类型的参数类不应导致错误。
我认为这个原则也适用于作业。赋值不会破坏任何语言规则,因此编译器不得引发错误。
因此,唯一可以将程序员从灾难中拯救出来的地方就是在add操作期间。但是编译器在那里能做什么呢?如果它因为赋值而不允许add操作objectList,那将破坏其他语言规则。如果它增强add以支持向 中添加对象numberList,那也会违反其他一些语言规则。
我想不出任何简单易行的解决方案不会破坏很多东西来修复甚至可能不是问题的东西,而程序员当然处于决定地位的好位置。
类型检查器旨在帮助程序员不要取代她。另一个不完美的例子:
public static void main(String[] args) {
Object m = args;
String[] m2 = m; //complains, despite m2 definitely being an String[]
}
PS:我在SO上找到了上面的例子,但不幸的是,我失去了链接!
添加回答
举报