我不明白编译器是如何处理下面的代码的,因为它输出了测试,而我正在等待一个错误。
List<Integer> b = new ArrayList<Integer>();
List a = b;
a.add("test");
System.out.println(b.get(0));我希望有人能告诉我编译器在执行代码时所经历的确切的步骤,以便我能够理解输出。我目前的理解是:
如果在实际的对象列表中没有add (对象e)方法,那么它如何仍然以某种方式将字符串添加到整数列表中?
发布于 2018-10-06 16:46:21
你们很亲密。编译时检查所有的结果:
a是List类型的,所以调用
a.add("test");平底锅。b是(编译时)类型的ArrayList<Integer>所以
b.get(0)也检查过了。请注意,检查只针对变量的编译时类型进行。当编译器看到a.add("test")时,它不知道变量a引用的对象的运行时值。一般说来,它真的不能(这在理论计算机科学上有一个结果),虽然控制流类型分析可以捕捉到许多这样的事情。像TypeScript这样的语言可以在编译时做一些令人惊奇的事情。
现在,您可能假设在运行时可以检查这些事情。唉,在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只在编译时根据变量的类型(在编译时已知)检查类型,但是泛型类型参数被删除,代码在没有它们的情况下运行。
发布于 2018-10-06 17:38:18
令人惊讶的是,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[]),它有不同的行为!
无论如何,不要使用原始的或罕见的类型,不要过载来改变行为(或者,如果您可以管理它的话,也不要使用重载),并且在对不可修复的规范进行优化之前,先考虑真正的。
https://stackoverflow.com/questions/52681096
复制相似问题