为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

阿波罗的战车 2021-11-03 10:49:55
我不明白编译器如何处理以下代码,因为它在我期待错误时输出测试。List<Integer> b = new ArrayList<Integer>();List a = b;a.add("test");System.out.println(b.get(0));我希望有人能告诉我编译器在执行代码时所经历的确切步骤,以便我能够理解输出。我目前的理解是:编译器会在编译时检查支持参数类型的 add 方法是否存在于以add(Object e)作为其原始类型的List类中。但是,在运行时,它尝试从实际对象List<Integer>调用 add(Object e) ,它不包含此方法,因为实际对象不是原始类型的,而是包含方法add(Integer e)。如果在实际对象List<Integer> 中没有 add(Object e) 方法,它如何仍然以某种方式将字符串添加到整数列表中?
查看完整描述

2 回答

?
哔哔one

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 仅在编译时根据变量的类型(在编译时已知)检查类型,但泛型类型参数会被删除,并且代码在没有它们的情况下运行。


查看完整回答
反对 回复 2021-11-03
?
拉丁的传说

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[])具有不同行为的过载!

无论如何,不要使用原始类型或稀有类型,不要重载来改变行为(如果你可以管理它,也不要重载),并在将优化提交到无法修复的规范之前要非常小心。


查看完整回答
反对 回复 2021-11-03
  • 2 回答
  • 0 关注
  • 164 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信