2 回答
TA贡献1854条经验 获得超8个赞
你很接近。编译时检查所有结果:
a
是类型List
所以调用
a.add("test");
平底锅。b
是(编译时)类型ArrayList<Integer>
所以
b.get(0)
退房。请注意,仅针对变量的编译时类型进行检查。当编译器看到a.add("test")
它不知道通过变量引用的对象的运行时间值a
。一般来说,它真的不能(在理论计算机科学中有一个关于这个的结果),尽管控制流类型分析可以捕捉到很多这样的事情。像 TypeScript 这样的语言可以在编译时做出惊人的事情。
现在您可能假设在运行时可以检查这些事情。唉,在 Java 中他们不能。Java 删除了泛型类型。查找有关 Java 类型擦除的文章以了解详细信息。TL;DR 是List<Integer>
编译时的 aList
在运行时变为原始的。JVM 没有办法“具体化”泛型(尽管其他语言有!)所以当引入泛型时,决定 Java 将删除泛型类型。所以在运行时,你的代码没有类型问题。
我们来看一下编译后的代码:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: astore_2
10: aload_2
11: ldc #4 // String test
13: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
18: pop
19: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
22: aload_1
23: iconst_0
24: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
29: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
32: return
在这里您可以直接看到没有运行时类型检查。因此,对您的问题的完整(但看似轻率)的答案是 Java 仅在编译时根据变量的类型(在编译时已知)检查类型,但泛型类型参数会被删除,并且代码在没有它们的情况下运行。
TA贡献1789条经验 获得超8个赞
令人惊讶的是,b.get(0)
它没有运行时检查。我们希望编译器解释的代码具有以下含义:
System.out.println((Integer)b.get(0)); // throws CCE
事实上,如果我们要尝试:
Integer str = b.get(0); // throws CCE
我们会得到一个运行时ClassCastException
。
事实上,我们甚至会得到相同的错误切换printf
代替println
:
System.out.printf(b.get(0)); // throws CCE
这有什么意义?
由于向后兼容,这是一个无法修复的错误。如果目标上下文可以允许删除检查转换,那么尽管更改了语义,它也会被忽略。在这种情况下,过载从println(Integer)
变为println(Object)
。比这更糟糕的是,有一个println(char[])
具有不同行为的过载!
无论如何,不要使用原始类型或稀有类型,不要重载来改变行为(如果你可以管理它,也不要重载),并在将优化提交到无法修复的规范之前要非常小心。
添加回答
举报