为什么编译器/JVM 不能让自动装箱“正常工作”?

发布于 2024-08-28 12:47:13 字数 677 浏览 4 评论 0原文

自动装箱相当可怕。虽然我完全理解 ==.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)

Autoboxing is rather scary. While I fully understand the difference between == and .equals I can't but help have the follow bug the hell out of me:

    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));

That prints

true
false

Why did they do it this way? It something to do with cached Integers, but if that is the case why don't they just cache all Integers used by the program? Or why doesn't the JVM always auto unbox to primitive?

Printing false false or true true would have been way better.

EDIT

I disagree about breakage of old code. By having foo.get(0) == bar.get(0) return true you already broke the code.

Can't this be solved at the compiler level by replacing Integer with int in byte code (as long as it is never assigned null)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

以往的大感动 2024-09-04 12:47:13
  • 他们为什么要这样做?

-128 到 127 之间的每个整数都由 java 缓存。据推测,他们这样做是为了提高性能。即使他们现在想反悔这个决定,他们也不太可能会这么做。如果有人根据此构建代码,那么当它被删除时,他们的代码就会崩溃。对于业余编码来说,这也许并不重要,但是对于企业代码来说,人们会感到不安,并且会发生诉讼。

  • 他们为什么不缓存程序使用的所有整数?

所有整数都不能被缓存,因为内存影响将是巨大的。

  • 为什么 JVM 不总是自动拆箱为原始类型?

因为JVM无法知道你想要什么。此外,此更改很容易破坏不是为处理这种情况而构建的遗留代码。

如果 JVM 在调用 == 时自动拆箱为原语,这个问题实际上会变得更加混乱。现在您需要记住 == 始终比较对象引用,除非可以拆箱对象。这会导致更多奇怪的令人困惑的情况,就像您上面所说的那样。

不要太担心这个问题,只需记住这条规则:

永远不要将对象与 == 进行比较,除非您打算通过引用来比较它们。如果您这样做,我无法想象您会遇到问题的情况。

  • Why did they do it this way?

Every Integer between -128 and 127 is cached by java. They did this, supposedly, for the performance benefit. Even if they wanted to go back on this decision now, it's unlikely that they would. If anyone built code depending on this, their code would break when it was taken out. For hobby coding, this perhaps doesn't matter, but for enterprise code, people get upset and lawsuits happen.

  • Why don't they just cache all Integers used by the program?

All Integers cannot be cached, because the memory implications would be enormous.

  • Why doesn't the JVM always auto unbox to primitive?

Because the JVM cannot know what you wanted. Also, this change could easily break legacy code not built to handle this case.

If the JVM to automatically unboxed to primitives on calls to ==, this issue will actually become MORE confusing. Now you need to remember that == always compares object references, unless the Objects can be unboxed. This would cause yet more weird confusing cases just like the one you stated above.

Rather then worry too hard about this, just remember this rule instead:

NEVER compare objects with == unless you intend to be comparing them by their references. If you do that, I can't think of a scenario in which you'd run into an issue.

葬﹪忆之殇 2024-09-04 12:47:13

您能想象如果每个 Integer 都承担滞留开销,性能会有多糟糕吗?也不适用于new Integer

Java 语言(不是 JVM 问题)不能总是自动拆箱,因为为 1.5 之前的 Java 设计的代码应该仍然可以工作。

Can you imagine how bad performance would be if every Integer carried overhead for internment? Also does not work for new Integer.

The Java language (not a JVM issue) cannot always auto unbox because code designed for pre-1.5 Java should still work.

淡忘如思 2024-09-04 12:47:13

字节范围内的整数是同一个对象,因为它们被缓存了。字节范围之外的整数则不然。如果要缓存所有整数,请想象一下所需的内存。

并来自此处

所有这些魔法的结果是,您可以在很大程度上忽略 int 和 Integer 之间的区别,但有一些注意事项。整数表达式可以有空值。如果您的程序尝试自动取消装箱 null,它将抛出 NullPointerException。 == 运算符对 Integer 表达式执行引用标识比较,对 int 表达式执行值相等比较。最后,即使是自动完成,装箱和拆箱也会带来性能成本

Integers in the byte range are the same object, because they are cached. Integers outside the byte range are not. If all integers were to be cached, imagine the memory required.

And from here

The result of all this magic is that you can largely ignore the distinction between int and Integer, with a few caveats. An Integer expression can have a null value. If your program tries to autounbox null, it will throw a NullPointerException. The == operator performs reference identity comparisons on Integer expressions and value equality comparisons on int expressions. Finally, there are performance costs associated with boxing and unboxing, even if it is done automatically

相权↑美人 2024-09-04 12:47:13

如果您完全跳过自动装箱,您仍然会遇到这种行为。

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 默认将自动装箱作为警告的原因。

If you skip autoboxing completely, you still get this behaviour.

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

Be more explicit if you want a specific behavior:

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

This is a reason, why Eclipse has autoboxing as a warning by default.

≈。彩虹 2024-09-04 12:47:13

很多人都对这个问题有疑问,甚至是写 Java 书籍的人。

Pro Java 编程,仅在几英寸以下,作者讨论了使用自动装箱整数作为 IdentityHashMap 中的键的问题,他在弱哈希映射。他使用的示例值大于 128,因此他的垃圾回收调用成功。如果有人使用他的示例并使用小于 128 的值,他的示例将会失败(由于密钥被永久缓存)。

A lot of people have problems with this issue, even people that write books about Java.

In Pro Java Programming, mere inches below were the author talks about issues with using auto-boxed Integers as a key in an IdentityHashMap, he uses auto-boxed Integer keys in a WeakHashMap. The example values he uses are greater than 128, so his garbage collection call succeeds. If someone were to use his example and use values smaller than 128 though, his example would fail (due to the key being perma-cached).

深海夜未眠 2024-09-04 12:47:13

当您编写

foo.get(0)

编译器时,如何创建列表并不重要。它只查看 List foo 的编译时类型。因此,如果这是一个 List,它会将其视为 List,正如它应该做的那样,并且 List的 get() 始终返回一个 Integer。如果你想使用 == 那么你必须写

System.out.println(foo.get(0).intValue() == bar.get(0).intValue());

not

System.out.println(foo.get(0) == bar.get(0));

因为它有完全不同的含义。

When you write

foo.get(0)

the compiler does not matter how you created the List. It only looks at the compile-time type of the List foo. So, if that is a List<Integer>, it will treat that as a List<Integer>, as it is supposed to do, and a List<Integer>'s get() always returns an Integer. If you want to use the == then you have to write

System.out.println(foo.get(0).intValue() == bar.get(0).intValue());

not

System.out.println(foo.get(0) == bar.get(0));

because that has a totally different meaning.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文