为什么编译器/JVM 不能让自动装箱“正常工作”?
-
25-09-2019 - |
题
自动装箱相当可怕。虽然我完全理解之间的区别 ==
和 .equals
我忍不住要摆脱以下错误:
final List<Integer> foo = Arrays.asList(1, 1000);
final List<Integer> bar = Arrays.asList(1, 1000);
System.out.println(foo.get(0) == bar.get(0));
System.out.println(foo.get(1) == bar.get(1));
那打印
true
false
他们为什么要这样做?这与缓存的整数有关,但如果是这样的话,为什么他们不缓存程序使用的所有整数呢?或者为什么 JVM 不总是自动拆箱为原始类型?
打印 false false 或 true true 会更好。
编辑
我不同意破坏旧代码。有了 foo.get(0) == bar.get(0)
返回 true 你已经破坏了代码。
难道不能在编译器级别通过在字节代码中用 int 替换 Integer 来解决这个问题吗(只要它永远不会被分配为 null)
解决方案
- 他们为什么要这样做?
-128 到 127 之间的每个整数都由 java 缓存。据推测,他们这样做是为了提高性能。即使他们现在想反悔这个决定,他们也不太可能会这么做。如果有人根据此构建代码,那么当它被删除时,他们的代码就会崩溃。对于业余编码来说,这也许并不重要,但对于企业代码来说,人们会感到不安,并且会发生诉讼。
- 他们为什么不缓存程序使用的所有整数?
所有整数都不能被缓存,因为内存影响将是巨大的。
- 为什么 JVM 不总是自动拆箱为原始类型?
因为JVM无法知道你想要什么。此外,此更改很容易破坏不是为处理这种情况而构建的遗留代码。
如果 JVM 在调用 == 时自动拆箱为原语,这个问题实际上会变得更加混乱。现在您需要记住 == 始终比较对象引用,除非可以拆箱对象。这会导致更多奇怪的令人困惑的情况,就像您上面所说的那样。
与其太担心这个问题,不如记住这条规则:
绝不 使用 == 比较对象,除非您打算通过引用来比较它们。如果您这样做,我无法想象您会遇到问题的情况。
其他提示
你能想象的表现多么糟糕是,如果每Integer
对拘留的开销进行?也不起作用为new Integer
。
在Java语言(不是一个JVM问题)不能总是自动拆箱,因为程序设计时,预先1.5 Java的应该仍然工作。
在字节范围Integer
s是同一个对象,因为它们被缓存。字节范围以外Integer
s不是。如果所有整数将被高速缓存的,想象所需的内存。
和从此处
这一切神奇的结果是,你可以在很大程度上忽略int和整数之间的区别,有几个注意事项。整数表达式可以具有一个空值。如果你的程序试图autounbox空,它会抛出一个NullPointerException。关于INT表达式整数表达式和数值相等比较==运算符执行参考同一性比较。最后,还有与装箱和拆箱相关的性能开销,即使是自动完成的。
如果你跳过自动装箱完全,你仍然可以得到这个行为。
final List<Integer> foo =
Arrays.asList(Integer.valueOf( 1 ), Integer.valueOf( 1000 ));
final List<Integer> bar =
Arrays.asList(Integer.valueOf( 1 ), Integer.valueOf( 1000 ));
System.out.println(foo.get(0) == bar.get(0)); // true
System.out.println(foo.get(1) == bar.get(1)); // false
如果你想有一个特定的行为更加明确:
final List<Integer> foo =
Arrays.asList( new Integer( 1 ), new Integer( 1000 ));
final List<Integer> bar =
Arrays.asList( new Integer( 1 ), new Integer( 1000 ));
System.out.println(foo.get(0) == bar.get(0)); // false
System.out.println(foo.get(1) == bar.get(1)); // false
这是有原因的,为什么Eclipse已经自动装箱作为默认的警告。
很多人都对这个问题有疑问,甚至是写 Java 书籍的人。
在 专业Java编程, 就在下面几英寸处,作者讨论了使用自动装箱的整数作为 IdentityHashMap 中的键的问题,他在 WeakHashMap 中使用了自动装箱的整数键。他使用的示例值大于 128,因此他的垃圾收集调用成功。如果有人使用他的示例并使用小于 128 的值,他的示例将会失败(由于密钥被永久缓存)。
当你写
foo.get(0)
编译器不要紧,你是如何创建的列表。它不仅外观在列表foo的编译时类型。所以,如果这是一个List
System.out.println(foo.get(0).intValue() == bar.get(0).intValue());
不
System.out.println(foo.get(0) == bar.get(0));
,因为其具有一个完全不同的含义。