Why can't the compiler/JVM just make autoboxing "just work"?
Asked Answered
S

6

13

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)

Soulier answered 8/4, 2010 at 19:0 Comment(5)
it just work!! not the way you expected though ;)Robalo
Actually your example has little to do with autoboxing, that behaviour predates it. It's true that autoboxing forces us to be more aware of it: equals() is the (usually correct) way of comparing objects, == is the (only) way of comparing primitives... autoboxing helps the programmer to treat Integer and int (almost) interchangeably... and hence the danger of bugs here.Mullin
This may be due to pecularities which also affect instances of String. As far as I understand things, there is some pooling taking place behind the scenes according to value. This can be prevented by using the new keyword.Discommend
If you're using Eclipse, you can enable a compiler warning for these cases: "Boxing and unboxing conversions" in Preferences > Java > Compiler > Errors/Warnings. Maybe there's also a similar warning in other IDEs, but AFAIK this is unfortunately not available when compiling from the command line.Suzetta
I consider your code to be bugged and not a problem with javac nor the JVM. See the response by @alexander-pogrebnyak below. I agree there's some confusion and unfortunate caching. But foo.get and bar.get return object reference to Integers. Your Lists are defined as containing Integers objects. Consequently, you should always perform .equals comparisons unless you are very clear and certain about why you aren't and fully understand what results you can expect based on the workings of the language and JVM. This is how Java works. If you don't like it, then code it correctly!Sendai
I
13
  • 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.

Incondite answered 8/4, 2010 at 19:20 Comment(7)
Never say Never. You should compare enum values with ==.Overmantel
@Alexander Pogrebnyak - You are correct, and that is why I added the "unless you intend to be comparing them by their references" clause. This covers enums. I stand by my never :)Incondite
Fair point. Then you still have your "License to kill -9" :). 007 outOvermantel
When would you ever not want to unbox to primitives?Egwan
Is there a reference that goes with this Integer caching behavior of Java?Doubleminded
Also, I don't see how this answers the original question, as the OP's test falls within the range you propose.Doubleminded
Here's a reference: martykopka.blogspot.com/2010/07/… If you look at the original question, OP's first println is comparing two Integers of value 1 (in the cache range), and OP's second println is comparing two Integers of value 1000 (not in the cache range). Therefore, when you get the 1's, the Integer objects returned both have the same reference, and are ==, but when you get the 1000's, the Integer objects returned do not have the same reference, so they are not == (but they are still equal by .equals)Incondite
S
7

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.

Sophy answered 8/4, 2010 at 19:4 Comment(2)
another +1 for mentioning backwards-compatibility issueEgwan
Because == returns true in my example. Before autoboxing it would return falseSoulier
S
5

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

Sought answered 8/4, 2010 at 19:11 Comment(2)
Integers outside of [-128, 127] may or may not be interned. (And I think the original question understands what is going on, but wants to know why?)Sophy
I think they aren't with sun's implementations. Anyway, it's about caching them and the resources required for caching.Sought
O
4

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.

Overmantel answered 8/4, 2010 at 19:28 Comment(4)
It's not on by default in my copy of Eclipse, and I haven't changed it. What version are you using? I've checked 3.2 and 3.4.Egwan
@Crhis: Eclipse Gallileo (3.5) Window->Preferences Java->Compiler->Errors/Warnings->Potential Programming Problems-> Boxing and Unboxing Conversions. Maybe it's not ON by default, but I had it on ever since I've switched to Java 5.Overmantel
It is definitely not on by default. I just changed it when I read about it here.Espousal
But it doesn't seem to do what I thought it would. I have: if (tsUser.user.id != connectedUser().id) but it does not give me a warning. Both of the id's are Longs.Espousal
U
3

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

Unbeaten answered 8/4, 2010 at 23:15 Comment(0)
D
2

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.

Discretionary answered 1/6, 2011 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.