Java - 将通用对象获取为String通用类型会引发异常

use*_*er7 7 java generics type-erasure generic-collections

public class Box<T> {
    private T element;

    public T getElement() {
        return element;
    }

    public void setElement(T element) {
        this.element = element;
    }
}

public class Test  {

    public static void main(String[] args) {
        List<Box> l = new ArrayList<>(); //Just List of Box with no specific type
        Box<String> box1 = new Box<>();
        box1.setElement("aa");
        Box<Integer> box2 = new Box<>();
        box2.setElement(10);

        l.add(box1);
        l.add(box2);

        //Case 1
        Box<Integer> b1 = l.get(0);
        System.out.println(b1.getElement()); //why no error

        //Case 2
        Box<String> b2 = l.get(1);
        System.out.println(b2.getElement()); //throws ClassCastException

    }
}
Run Code Online (Sandbox Code Playgroud)

该列表l包含类型的元素Box.在第一种情况下,我获得第一个元素Box<Integer>,在第二种情况下,列表中的第二个元素获得为Box<String>.在第一种情况下不抛出ClassCastException.

当我试图调试中,element's在类型b1b2StringInteger分别.

它与类型擦除有关吗?

Ideone链接

Tom*_*Tom 4

准确来说,问题是PrintStream#println

\n

让我们使用以下命令检查编译后的代码javap -c Test.class

\n
72: invokevirtual #12        // Method blub/Box.getElement:()Ljava/lang/Object;\n75: invokevirtual #13        // Method java/io/PrintStream.println:(Ljava/lang/Object;)V\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所看到的,编译器删除了类型,并且还省略了 的强制转换Integer,因为这里没有必要。编译器已将使用的重载方法链接到PrintStream#(Object)。\n由于JLS 规则 \xc2\xa75.3 ,它会这样做:

\n
\n

方法调用转换应用于方法或构造函数调用中的每个参数值(\xc2\xa78.8.7.1\xc2\xa715.9\xc2\xa715.12):参数表达式的类型必须转换为对应参数的类型。

\n

方法调用上下文允许使用以下之一:

\n\n
\n

第三条规则是从子类型到超类型的转换:

\n
\n

存在从任何引用类型 S 到任何引用类型 T 的扩大引用转换,前提是 S 是 T 的子类型 ( \xc2\xa74.10 )。

\n
\n

并且在检查类型是否可以拆箱之前完成(第五次检查:“拆箱转换”)。因此编译器会检查它Integer是 的子类型Object,因此它必须调用#println(Object)(如果您检查被调用的重载版本,您的 IDE 会告诉您相同的信息)。

\n

另一方面,第二个版本:

\n
 95: invokevirtual #12        // Method blub/Box.getElement:()Ljava/lang/Object;\n 98: checkcast     #14        // class java/lang/String\n101: invokevirtual #15        // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n
Run Code Online (Sandbox Code Playgroud)\n

有一个checkcast检查检索到的类型Box#getElement是否确实是 a String。这是必要的,因为您告诉编译器它将是 a String(由于泛型类型Box<String> b2 = l.get(1);)并且它链接了 method PrintStream#(String)。此检查因上述内容而失败ClassCastException,因为Integer无法将其强制转换为String.

\n