In an object oriented world there are basically two types of ‘equality’ knocking around. The simplest version is pure object identity. In this form, two variables are ‘identical’ only if they refer to the exact same object on the heap. Conversely, if two variables refer to different in-memory objects then they are not identical regardless of the contents of the object.
So, if we have
Object a = new Object();
Object b = new Object();
then a and b refer to different objects and so, in terms of object identity, are not equal. If, on the other hand, we have
Object a = new Object();
Object b = a;
then a and b refer to exactly the same object and so they’re equal.
This mechanism for comparing objects also corresponds with some of our common sense ideas about equality – physical objects are generally considered to be different in some sense even if they share characteristics which make them equivalent in some way. In a JVM this form of equality is modelled by the ‘==’ operator..
Where this gets complicated is that the more mathematical notion of equality, where values can be equal even if they’re contained in distinct objects, is used in Java for primitive types. Primitive types adhere to the mathematical notion of equality whereas objects in the object hierarchy adhere to the object identity model of equality. So,
int i = 42;
int j = 42;
assert i == j;
but
Object a = new Object();
Object b = new Object();
assert a != b;
Things get further complicated when we start considering boxed primitives. The Java language has two ways of representing a value of one of the primitive types (bool, char, short, int, long, float or double). It can either be a primitive value (e.g. long longValue = 42L) or it can be ‘boxed’ into an object (e.g. Long longValue = Long.valueOf(42L)). The behaviour of primitives and their boxed equivalents is different in terms of identity – boxed primitives adhere to the object notion of identity. So, we have,assert 1L == 1L;
assert new Long(1L) != new Long(1L);
Things can get even more confusing when you take into account how the boxed types are designed to aggressively cache and re-use instances which have the same value. So, if you use the static factory methods to create an instance of the boxed types you’re likely to get a pre-existing instance…assert Long.valueOf(1) == Long.valueOf(1L);
assert Long.valueOf(1) != new Long(1L);
And you need to take care because not everything gets cached…assert Long.valueOf(987654321L) != Long.valueOf(987654321L));
So far, so confusing. There’s one last step however – when you mix primitive values with their boxed equivalents you can get some unexpected behaviour. When the Java compiler encounters a statement which mixes primitive values with their boxed equivalents it automatically converts between boxed and unboxed versions of things, depending on which form it needs to ensure type safety, so that the entire expression is either all primitives or all boxed.assert Long.valueOf(987654321L) != Long.valueOf(987654321L);
assert Long.valueOf(987654321L) == 987654321L;
None of this even begins to delve into what ‘==’ means for floating point numbers, of type float or double.
In summary, then, the ‘==’ operator in Java is a tricksy beast to use correctly and should generally not be relied upon when you’re mixing primitives with their boxed versions.
Leave a Reply