意外地将 String 添加到 List<Integers>

我不明白编译器如何处理以下代码,因为它在我期待错误时输出测试。


List<Integer> b = new ArrayList<Integer>();

List a = b;

a.add("test");

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

我希望有人能告诉我编译器在执行代码时所经历的确切步骤,以便我能够理解输出。我目前的理解是:

  1. 编译器会在编译时检查支持参数类型的 add 方法是否存在于以add(Object e)作为其原始类型的List类中。

  2. 但是,在运行时,它尝试从实际对象List<Integer>调用 add(Object e) ,它不包含此方法,因为实际对象不是原始类型的,而是包含方法add(Integer e)

如果在实际对象List<Integer> 中没有 add(Object e) 方法,它如何仍然以某种方式将字符串添加到整数列表中?


阿波罗的战车
浏览 172回答 2
2回答

哔哔one

你很接近。编译时检查所有结果: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 将删除泛型类型。所以在运行时,你的代码没有类型问题。我们来看一下编译后的代码:&nbsp;0: new&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;#2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // class java/util/ArrayList&nbsp; &nbsp;3: dup&nbsp; &nbsp;4: invokespecial #3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Method java/util/ArrayList."<init>":()V&nbsp; &nbsp;7: astore_1&nbsp; &nbsp;8: aload_1&nbsp; &nbsp;9: astore_2&nbsp; 10: aload_2&nbsp; 11: ldc&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;#4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // String test&nbsp; 13: invokeinterface #5,&nbsp; 2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z&nbsp; 18: pop&nbsp; 19: getstatic&nbsp; &nbsp; &nbsp;#6&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Field java/lang/System.out:Ljava/io/PrintStream;&nbsp; 22: aload_1&nbsp; 23: iconst_0&nbsp; 24: invokeinterface #7,&nbsp; 2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;&nbsp; 29: invokevirtual #8&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Method java/io/PrintStream.println:(Ljava/lang/Object;)V&nbsp; 32: return在这里您可以直接看到没有运行时类型检查。因此,对您的问题的完整(但看似轻率)的答案是 Java 仅在编译时根据变量的类型(在编译时已知)检查类型,但泛型类型参数会被删除,并且代码在没有它们的情况下运行。

拉丁的传说

令人惊讶的是,b.get(0)它没有运行时检查。我们希望编译器解释的代码具有以下含义:System.out.println((Integer)b.get(0));&nbsp;//&nbsp;throws&nbsp;CCE事实上,如果我们要尝试:Integer&nbsp;str&nbsp;=&nbsp;b.get(0);&nbsp;//&nbsp;throws&nbsp;CCE我们会得到一个运行时ClassCastException。事实上,我们甚至会得到相同的错误切换printf代替println:System.out.printf(b.get(0));&nbsp;//&nbsp;throws&nbsp;CCE这有什么意义?由于向后兼容,这是一个无法修复的错误。如果目标上下文可以允许删除检查转换,那么尽管更改了语义,它也会被忽略。在这种情况下,过载从println(Integer)变为println(Object)。比这更糟糕的是,有一个println(char[])具有不同行为的过载!无论如何,不要使用原始类型或稀有类型,不要重载来改变行为(如果你可以管理它,也不要重载),并在将优化提交到无法修复的规范之前要非常小心。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java