我不知道编译器如何处理以下代码,因为在我期望出现错误时它会输出 Test 。
List<Integer> b = new ArrayList<Integer>();
List a = b;
a.add("test");
System.out.println(b.get(0));
我希望有人可以告诉我编译器执行代码时执行的 exact 步骤,这样我才能理解输出。我目前的理解是:
如果实际对象 List
答案 0 :(得分:4)
您非常亲密。编译时检查所有结果:
a
的类型为List
,因此通话
a.add("test");
显示。 b
的类型(编译时)为ArrayList<Integer>
,因此
b.get(0)
也要签出。请注意,检查仅针对变量的编译时类型进行。当编译器看到a.add("test")
时,它不不知道变量a
所引用的对象的运行时值。总的来说,它实际上是不可以的(理论上计算机科学对此有一个结果),尽管控制流类型分析可以捕获许多这样的东西。诸如TypeScript之类的语言可以在编译时完成令人惊奇的事情。
现在,您可能假设可以在运行时检查此类情况。 Java,在Java中,它们不能。 Java擦除通用类型。查找有关Java类型擦除的文章,了解更多细节。 TL; DR是在编译时将List<Integer>
变成运行时的原始List
。 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仅在编译时根据变量的类型(在编译时已知)检查类型,但是通用类型参数被删除并代码在没有它们的情况下运行。
答案 1 :(得分:0)
这里令人惊讶的是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[])
的重载具有不同的行为!
无论如何,请不要使用原始或稀有类型,也不要重载来更改行为(如果可以管理,也不要重载),并且在对不可修复的优化进行优化之前要认真对待。规格。