首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >意外地向List<Integers>添加字符串

意外地向List<Integers>添加字符串
EN

Stack Overflow用户
提问于 2018-10-06 16:31:14
回答 2查看 113关注 0票数 5

我不明白编译器是如何处理下面的代码的,因为它输出了测试,而我正在等待一个错误。

代码语言:javascript
复制
List<Integer> b = new ArrayList<Integer>();
List a = b;
a.add("test");
System.out.println(b.get(0));

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

  1. 编译器在编译期间检查支持参数类型的add方法是否存在于List类中,即add(对象e)作为其原始类型。
  2. 但是,在运行时,它尝试从实际对象List调用add(对象e),它不包含此方法,因为实际对象不是原始类型的,而是持有方法add(Integer e)

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-10-06 16:46:21

你们很亲密。编译时检查所有的结果:

aList类型的,所以调用

代码语言:javascript
复制
a.add("test");

平底锅。b是(编译时)类型的ArrayList<Integer>所以

代码语言:javascript
复制
b.get(0)

也检查过了。请注意,检查只针对变量的编译时类型进行。当编译器看到a.add("test")时,它不知道变量a引用的对象的运行时值。一般说来,它真的不能(这在理论计算机科学上有一个结果),虽然控制流类型分析可以捕捉到许多这样的事情。像TypeScript这样的语言可以在编译时做一些令人惊奇的事情。

现在,您可能假设在运行时可以检查这些事情。唉,在Java中,他们不能。Java擦除泛型类型。查找一篇关于Java类型擦除的文章,以了解血淋淋的细节。TL;DR是编译时的List<Integer>在运行时变成了原始的List。JVM没有一种“具体化”泛型的方法(尽管其他语言是这样做的!)因此,在引入泛型时,决定Java只删除泛型类型。因此,在运行时,代码中没有类型问题。

让我们看一看编译后的代码:

代码语言:javascript
复制
   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只在编译时根据变量的类型(在编译时已知)检查类型,但是泛型类型参数被删除,代码在没有它们的情况下运行。

票数 4
EN

Stack Overflow用户

发布于 2018-10-06 17:38:18

令人惊讶的是,b.get(0)没有运行时检查。我们期望编译器对代码进行解释,其含义如下:

代码语言:javascript
复制
System.out.println((Integer)b.get(0)); // throws CCE

事实上,如果我们要尝试:

代码语言:javascript
复制
Integer str = b.get(0); // throws CCE

我们会得到一个运行时ClassCastException

实际上,我们甚至会得到相同的错误切换printf而不是println

代码语言:javascript
复制
System.out.printf(b.get(0)); // throws CCE

那又有什么意义?

这是一个错误,不能修复,因为向后兼容性。如果目标上下文允许删除检查强制转换,那么尽管更改了语义,它还是会被省略。在这种情况下,重载从println(Integer)更改为println(Object)。更糟糕的是,有一个过载的println(char[]),它有不同的行为!

无论如何,不要使用原始的或罕见的类型,不要过载来改变行为(或者,如果您可以管理它的话,也不要使用重载),并且在对不可修复的规范进行优化之前,先考虑真正的

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52681096

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档