Not long ago, we had a not too nice issue related to mismatch between int and long variables. As the concrete variables were coming from 1) a model created by others and 2) another model created by us, the solution did not appear to be simple.
We were thinking, if we could compare the variables by value consistently without the concrete type equality, we would not need to support both int and long numbers in our model, so we asked our collegue, Gábor Bergmann to experiment a bit with the comparison.
Basically, when comparing primitive types and their boxed values, if you have two different types, all hell’s might break loose. Sometimes two values are considered equal when using ‘==’ but not when using Object.equals. Even worse, transitivity can also be broken.
I cannot think about any possible reason for this behavior, but we could reproduce it issue consistently across different computers using Java 1.6, so this appears to be according to be designed that way.
If someone could provide some reasonable explanation, I would be glad for it. Then we could learn something nice instead of just laughing a bit. Otherwise, be careful with primitive comparison…
12 thoughts on “Java primitive type comparison – A wat look”
You’re right; this is broken. I blogged similar behavior too:
I think you might want to look at the book Java Puzzlers. Perhaps it could shed some light on the issue 🙂
// false, it only equals 1L – so much for semantic equivalence
is clear because autoboxing makes the 1 to Integer.valueOf(1) and the equals method of java.lang.Object only accepts an Object and if you use
System.out.println(new Long(1).equals(1L)); you tell the compiler to use Long.valueOf(1L) instead of Integer.valueOf(1)
System.out.println(new Integer(1).equals(new Long(1)));
// false… Y U NO EQUAL?
is also clear just have a look at the javaDoc of java.langObject#equals(Object) (http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#equals%28java.lang.Object%29)
System.out.println(new Long(1) == 1);
this works also because the compiler makes the following code ouf of your code:
System.out.println(new Long(1L).longValue() == 1L);
and longValue() is the autoboxing method of a Long object to a long datatype
and everytime you use the == it compares the references not the content
@WhoCares thanks for your evaluation. First of all, I can understand the non-equality when using equals – although it is not what I would expect from it, but it is acceptable.
Just think about it what is easier and faster in the situation of comparing primitive datatypes? Checking two primitive datatypes which could afterwards be done by simple machine instruction or comparing two references of objects which could probably differ.
A conversion of
System.out.println(new Long(1) == 1);
System.out.println(new Long(1) .equals( new Long(1) );
would cause the creation of another Long object; a equals method call and some checks inside of the equals method.
The call of (new Long(1L).longValue() == 1L) which the compiler produces just calls one method and afterwards a simple compare of two registry/memory entries which is much faster. (BTW if this method is called multiple times maybe in a loop the HotSpot JIT compiler will have a chance to optimize this to a more efficent code without a method call.)
@WhoCares thanks for linking to the javaDoc, but can you point out how you think it explains the behaviour of equals() in this case? new Long(1).equals(new Integer(1)) would not necessarily violate any of the axioms (reflexivity, transitivity, etc.). So the question remains: why did they choose an implementation where equals() returns false in this case? See David Orme’s blog post above for a much more articulate explanation why this implementation has to be considered broken 🙂
“this works also because the compiler makes the following code ouf of your code” – of course. But the real question is: _why_ does the compiler make 1L out of the right-hand-side, instead of e.g. raising a compile error, such as when one tries to compare Integer with Long?
@WhoCares Sorry, I was not clear. I could understand that System.out.println(new Long(1) == 1) returns false, because the second argument is not a long. Similarly, I could understand that it returns true, because the values are the same.
Similarly, I could understand the same things for equals as well, but I would like a consistent output, that maintains transitivity as well, instead of the current implementation.
It would violet the symmetry, because new Long(1L).equals(new Integer(1)) would return true and new Integer(1).equals(new Long(1L)) would return false. To solve this you would need a dependency from java.lang.Long to java.lang.Integer and vica versa and this is not good. (for more information have a look at the book: Effective Java 2nd Edition by Joshua Bloch – Item 8: Obey the general contract when overriding equals << this guy can explain this a lot better than me and he also shows you all the pitfalls with a lot of examples)
I can't see any inconsistency because this is the default way of unboxing and auto casting.
@WhoCares I begin to see your point, and thank you for showing it.
I understand that this is the default way of unboxing and autocasting, but there are some basic expectations that become violated because of that: first of all, if ‘o1==o2’, I also expect ‘o1.equals(o2)’ be true as well. So, in other words, if ‘new Long(1) == 1’ would return false, everything would be fine by me.
Luckily, the issue is not common, but when it comes forth, it needs some time to understand correctly.
Have you ever thought about why you really need those wrapper classes (java.lang.Long, java.lang.Double, …) and way they are called “wrapper classes”?
@WhoCares: of course, I meant a symmetric modification in both classes (I did not mention it, because I assumed it to be trivial). Java was explicitly designed to allow circular dependencies between classes, so I’m afraid your counter-argument does not hold. For an example from the core JRE, see the circular dependency between java.lang.String and java.lang.StringBuilder.
So, once again, why isn’t there consistency between == and equals()? It is either the implementation of equals() in Integer, Long, etc. that should be fixed, or the == operator between primitives. Preferably, it is the former, because there really is semantical equivalence between 1L and 1, so == should hold as well as equals().
Apples and pears are never equal even though they’re both green…