views:

341

answers:

5

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)

+7  A: 

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.

Tom Hawtin - tackline
good mention of pre-1.5 code. +1
Bozho
another +1 for mentioning backwards-compatibility issue
Chris
they already broke old code by caching -128 to 127
Pyrolistical
@Pyrolistical How did that break old code??
Tom Hawtin - tackline
Because == returns true in my example. Before autoboxing it would return false
Pyrolistical
+4  A: 

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

Bozho
`Integer`s 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?)
Tom Hawtin - tackline
I think they aren't with sun's implementations. Anyway, it's about caching them and the resources required for caching.
Bozho
+4  A: 
  • 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.

Jon Quarfoth
Never say **Never**. You **should** compare `enum` values with ==.
Alexander Pogrebnyak
@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** :)
Jon Quarfoth
@Jon - Fair point. Then you still have your "License to kill -9" :). 007 out
Alexander Pogrebnyak
When would you ever *not* want to unbox to primitives?
Chris
+2  A: 

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.

Alexander Pogrebnyak
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.
Chris
@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.
Alexander Pogrebnyak
A: 

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

Trevor Harrison