为什么可以从 Java 中的参数化列表中取回 "incorrect-type" 的对象?

Why is it possible to get back an object of "incorrect-type" from the parametrized List in Java?

这是一个代码片段:

import java.util.*;
class Test
{
    public static void main(String[] args)
    {
        List<Integer> list = new ArrayList<>();
        addToList(list);
        Integer i = list.get(0); //#1 fails at run-time
        String s = list.get(0); //#2 fails at compile-time
        list.get(0); //#3 works fine
        System.out.println(list.get(0)); //#4 works fine, prints "string"
    }
    static void addToList(List list){
        list.add("string");
    }
}

我明白为什么可以在参数化列表中插入字符串 class 的对象。

看来我明白为什么标有 #1#2 的代码会失败。

但为什么 #3#4 有效?据我所知,编译器在类型擦除后添加了适当的强制转换,因此当我调用 list.get(0) 时,此方法应该 return 之前已强制转换为 Integer 的对象。那么为什么在 运行 时间的#3 和#4 没有发生 ClassCastException?

转换应用于 get 的 return 类型,而不是引入污染的 add。否则,您将在此时遇到编译时错误或异常,因为您无法将 String 转换为 Integer.

#3 起作用是因为 get(int) 返回的对象被忽略了。存储在位置 0 的任何内容都会返回,但由于没有转换,因此不会发生错误。

#4 出于同样的原因工作正常:get(0) 生成的对象被视为 println 中的 java.lang.Object 子类,因为 toString 被调用。由于 toString() 可用于所有 Java 对象,调用完成时没有错误。

首先是为什么可以将字符串添加到 List<Integer> 的原因。在方法

static void addToList(List list){

您使用 原始类型。原始类型的存在纯粹是为了与旧 Java 版本兼容,不应在新代码中使用。在 addToList 方法中,Java 编译器不知道 list 应该只包含整数,因此在向其添加字符串时它不会抱怨。

至于你们两个说法的不同行为。 Integer i = list.get(0) 不会在编译时失败,因为 Java 认为 list 只包含 Integer。只有在 运行 时才发现 list 的第一个元素不是整数,因此你得到 ClassCastException.

String s = list.get(0) 在编译时失败,因为 Java 编译器假定 list 仅包含整数,因此它假定您尝试将整数分配给字符串引用。

只是list.get(0)不存储方法调用的结果。因此,无论是在编译时还是在 运行 时,都没有任何失败的原因。

最后,System.out.println(list.get(0)) 起作用了,因为 System.out 是一个 PrintStream 并且有一个 println(Object) 方法,可以用 Integer 参数调用。

4:正在调用重载 System.out.println(Object),因为 Integer <=_T Object(阅读:Integer is-a Object)。请注意,list.get(int) returns 一个对象在 运行 时间内,因为类型参数被擦除。现在阅读

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

这告诉您 "Insert type casts if necessary to preserve type safety.",因为从对象到对象不需要类型转换,所以不会产生 ClassCastException。

出于同样的原因,在 3 处没有类型转换,但是 List.get 具有 none.

的方法调用可能会产生副作用

如果你看看ArrayList#get方法。是这样的:

public E get(int index) {
   //body
}

但在运行时实际上是:

public Object get(int index) {
       //body
}

所以当你Integer i = list.get(0); 编译器将其转换为:

Integer i = (Integer)list.get(0);

现在在运行时,list.get(0) returns 一个 Object 类型(实际上是 String)。它现在尝试转换 String => Integer 但失败了。

3

因为它只是:

list.get(0)

编译器确实向任何内容添加了类型转换。所以它只是,list.get(0).

4

System.out.println(list.get(0));

list.get(0) returns Object 类型。因此调用 PrintStream 的 public void println(Object x) 方法。

记得println(Object x) gets called and not println(String x).