Java 1.5 Autoboxing Wackyness!

After reading some odd posts, accusations, and recriminations on Digg, I decided to take a peek at how integers work in Java 1.5. Observe this simple snippet from the middle of my test:

i1 = 127; i2 = 127; i3 = 127;
System.out.println( i1==i2);
System.out.println( i2==i3);

i1 = 128; i2 = 128; i3 = 128;
System.out.println( i1==i2);
System.out.println( i2==i3);

Looks simple enough... set three integers to the same number, test if they are the same, and print true if they are equal... but here's the output:

true
true
true
false

Analysis: apparently 128 does not always equal 128... Whoops!

My gut feeling is that this should never be possible. It wasn't in Java 1.4, or any other language I know... Of course, I left out the most important part of my test code -- the declaration:

int i1;
Integer i2;
Integer i3;

Now it should be clearer to the Java bunnies... The variable i1 is an integer primitive, whereas i2 and i3 are Integer objects. As we all know, the equals operator i2==i3 tests if two things are the exact same object in the computer's memory... whereas the equals method i2.equals(i3) tests if they have the same value. That's an important distinction for any object-oriented language.

So, what's the big deal? This was always a problem, right? You'd have the same issues in all versions of Java.

Yes, but that's not the point... In older versions of Java the code would have to be this:

i1 = 128;
i2 = new Integer(128);
i3 = new Integer(128);

Aha! More clear now... no matter what we know that i2 is an Integer object, and we need to use the equals method... But Java 1.5 uses autoboxing, which is causing a bit of a confusion.

Autoboxing is basically shorthand. It allows you to define and change the value of objects as if they were primitives. The first code snippet would never compile in Java 1.4, and nobody was confused about it. Now, I'm all for shorthand... but this is going to cause more problems than it solves.

If you just take the first code snippet, any reasonable person would balk if all tests didn't return true. If you were forced to set the new values as if they were objects, then any proficient Java hacker would know to use the equals method. But if you use this shorthand all over, you might forget if that variable is an object or a primitive.

Even if you do use the equals operator with numbers, you still have to be careful with types. There are four kinds of integer objects, Byte, Short, Integer, and Long. Count 'em, four! Naturally, if you want to compare numbers, you need to use the equals operator. However, you better make sure that the objects are the same type... otherwise zero will not equal zero:

Long l1 = 0L; // autoboxing shorthand
System.out.println(l1.equals(0L)); // true
System.out.println(l1.equals(0)); // false

The first test compares a Long with value zero to another Long with value zero... the second compares it with a Integer with value zero. Although they are both integers in the abstract sense, they are different types of objects, so the equals operator fails. Zero does not always equal zero.

I stand by my assertion that object-oriented zealots encourage people to abstract away the wrong information...

Note, this behavior makes total sense for almost all objects: if they are not the same type, how can the equals method possibly make any sense? But some argue, as I do, that primitives are already special cases, and should be treated as such... especially when you are talking about integers. Every hacker knows you shouldn't compare a zero integer to a zero floating-point number... but zero should equal zero for all integers, regardless of sub-type.

Sun should have tried harder to make sure things make the most sense to the most people... they shouldn't simply pander to those who drank the object-oriented kool-aid. Somebody from the C programming world would expect a compiler warning... people from the PHP/Python/Perl/Ruby camp would expect it to 'just work'. Even Java 1.4 folks would look at it sideways for a moment...

In either case, prevent the uninitiated from writing financial apps in Java!

Full test code:


public class AutoboxingTest 
{
	public static void main(String[] args) 
	{
		int i1 = 127;
		Integer i2 = 127;
		Integer i3 = 127;
		System.out.println( i1==i2); // true
		System.out.println( i2==i3); // true

		i1 = 128;
		i2 = 128;
		i3 = 128;
		System.out.println( i1==i2); // true
		System.out.println( i2==i3); // false

		i1 = -128;
		i2 = -128;
		i3 = -128;
		System.out.println( i1==i2); // true
		System.out.println( i2==i3); // true

		i1 = -129;
		i2 = -129;
		i3 = -129;
		System.out.println( i1==i2); // true
		System.out.println( i2==i3); // false
		
		Long l1 = 0L;
		System.out.println(l1.equals(0L)); // true
		System.out.println(l1.equals(0)); // false
	}
}

comments

behaviour explained

For values between the range of -128 to 127 Java points all the Integer/ int references to the same object to save memory. Hence Integer.equals(int) returns true.

Also for the .equals method states that for two objects to be equal they have to be of the SAME TYPE and have a SAME VALUE.

Hence Long.equals(Integer) returns false for 0.

i'm sure you would have definitely known this , but the answer is for people who visit this site and discover the weird behavior of Auto boxing in Java and are left pondering without an answer ;)

Regards,
Goutham

It would seem strange

If you hadn't read the specification. For some, Java's type system seems restrictive. At least for me, a weak type system is more restrictive. Furthermore, I argue that the "uninitiated" shouldn't write apps (financial or otherwise) in any language!

Of course, if you really want the behavior you describe there is always the option of rolling your own subclass of Long and overriding Long.equals() to do implicit widening of ints and shorts. Yay for O-O!

What's funny is that the

What's funny is that the integer 0 does equal the floating point 0.0, because of how floats are encoded.

Integer and new Integer()

Integer a = 10;
Integer b = 10;

Integer x = new Integer(10);
Integer y = new Integer(10);

int p = 10;
int q = 10;

a==b true
x==y false
p==q true

Does anybody know why a==b and x==y are different? I thought autoboxing mean't that "Integer a=10" was short hand for "Integer a = new Integer(10)" ?

Cheers
Ste

Re: Integer and new Integer()

Ste:
To quote Goutham's comment:
"For values between the range of -128 to 127 Java points all the Integer/ int references to the same object to save memory. Hence Integer.equals(int) returns true."

Thus "Integer a=10" is not really shorthand for "Integer a = new Integer(10)". The compiler automatically makes it a reference to a static object for 10, since it's between -128 and 127.

Float c = new Float

Float c = new Float (210f);
int p = 210;
System.out.println(c==p); //true , not able to understand how referance can be same
System.out.println(c.equals(p)); //false its ok

it gets worse....

As visitor before pointed out, consider the following code

Long a= 1L;
Long b= 1L;
System.out.println("a==b: "+(a==b));
System.out.println("a.equals(b): "+a.equals(b));
Long c=new Long(1L);
System.out.println("a==c: "+(a==c));
System.out.println("a.equals(c): "+a.equals(c));

the output is

a==b: true
a.equals(b): true
a==c: false
a.equals(c): true

which is crazy. It seems that you must do shorthand in order for autoboxing to work. I just hunted down a bug due to this. Working on some Google Web Toolkit code. There are many final copies of objects in the code and it almost seemed like because both the dev appengine server was running the same JVM the copies of the objects were considered equal and everything worked but once it got recompiled and deployed, the autoboxing ceased to work. I'm back to .equals.

in java 1.6 this is not any more true ...

in java 1.6 this is four times true like expected ...

i1 = 127; i2 = 127; i3 = 127;
System.out.println( i1==i2);
System.out.println( i2==i3);

i1 = 128; i2 = 128; i3 = 128;
System.out.println( i1==i2);
System.out.println( i2==i3);

Looks simple enough... set three integers to the same number, test if they are the same, and print true if they are equal... but here's the output:

true
true
true
true

in java 1.6 this is also true ..

I think your example was not correct:
Use:
int i1 = 127;
Integer i2 = 127;
Integer i3 = 127;

AND NOT:
int i1 = 127, i2 = 127, i3 = 127;

It behaves the same in jdk 7 as well

Run the program online here.
http://ideone.com/fG4kSa

Recent comments