Why are these == but not `equals()`?
Asked Answered
R

8

24

I'm a bit confused about the way Java treats == and equals() when it comes to int, Integer and other types of numbers. For example:

Integer X = 9000;
int x = 9000;
Short Y = 9000;
short y = 9000;
List<Boolean> results = new ArrayList<Boolean>();
// results.add(X == Y); DOES NOT COMPILE        1)
results.add(Y == 9000);                      // 2)
results.add(X == y);                         // 3)
results.add(X.equals(x));                    // 4)
results.add(X.equals(Y));                    // 5)
results.add(X.equals(y));                    // 6)
System.out.println(results);

outputs (maybe you should make your guess first):

[true, true, true, false, false]
  1. That X == Y does not compile is to be expected, being different objects.
  2. I'm a little surprised that Y == 9 is true, given that 9 is by default an int, and given that 1) didn't even compile. Note that you can't put an int into a method expecting a Short, yet here they are equal.
  3. This is surprising for the same reason as two, but it seems worse.
  4. Not surprising, as x is autoboxed to and Integer.
  5. Not surprising, as objects in different classes should not be equal().
  6. What?? X == y is true but X.equals(y) is false? Shouldn't == always be stricter than equals()?

I'd appreciate it if anyone can help me make sense of this. For what reason do == and equals() behave this way?

Edit: I have changed 9 to 9000 to show that this behavior is not related to the any unusual ways that the integers from -128 to 127 behave.

2nd Edit: OK, if you think you understand this stuff, you should consider the following, just to make sure:

Integer X = 9000;
Integer Z = 9000;
short y = 9000;
List<Boolean> results = new ArrayList<Boolean>();
results.add(X == Z);                      // 1)
results.add(X == y);                      // 2)
results.add(X.equals(Z));                 // 3)
results.add(X.equals(y));                 // 4)
System.out.println(results);

outputs:

[false, true, true, false]

The reason, as best as I understand it:

  1. Different instance, so different.
  2. X unboxed, then same value, so equal.
  3. Same value, so equal.
  4. y cannot be boxed to an Integer so cannot be equal.
Richie answered 11/8, 2009 at 11:8 Comment(2)
the original question was with X = x = Y = y = 9, so in the "caching area" of Integer/ShortFishworm
Returning to this question five years later, I see that I no longer have any idea what to expect. So glad that I'm not spending my time thinking about such nonsense anymore, using Python these days.Richie
V
13

The reason for

X == y

being true has to do with binary numeric promotion. When at least one operand to the equality operator is convertible to a numeric type, the numeric equality operator is used. First, the first operand is unboxed. Then, both operands are converted to int.

While

X.equals(y)

is a normal function call. As has been mentioned, y will be autoboxed to a Short object. Integer.equals always returns false if the argument is not an Integer instance. This can be easily seen by inspecting the implementation.

One could argue that this is a design flaw.

Vickery answered 11/8, 2009 at 11:26 Comment(8)
@waxwing: "One could argue that this is a design flaw" ... caused by listening to people who were clamoring for C# features in Java.Ortolan
With my second edit above, I've offered a different example to show how problematic this behavior is.Richie
@Stephen C: I hate to burst your bubble, but that's not entirely fair. To start with, C# has no distinction between a boxed int (aka Integer in Java) and an inline/primitive int. There's no way to have a variable typed as integer reference a boxed integer. The code would return true,true,true,true in C# (int.Equals(int) helps with 4). However, when you'd explicitly use object references, you would get the same results.Underworld
(And no, C# does not have auto-unboxing; unboxing is always explicit in C#.)Underworld
@Ruben: my bubble is not burst! I was not making a statement about C# at all. Rather I was commenting on the people who whined and whinged about not having boxing/unboxing in Java pre 1.5. The Java language designers should have just left well alone.Ortolan
Different situations require different equality semantics. Confusion from difference in == and equals stem mainly from a mistaken belief they should be equivalent. On the other hand, if I had my druthers, the == operator would expressly forbid certain combinations of operands even in cases where one could be implicitly cast to the other. For example, comparisons between a double and either a long or float could be made by casting the double to the other type or casting the other type to double. Which operation is correct would depend upon circumstances.Portiere
As implemented, Java performs the indicated comparisons by casting both operands to double; in some cases, that may be correct behavior, but in other cases it is not. Requiring the program to perform an explicit cast would avoid the ambiguity. Some comparisons only have one sensible behavior (e.g. between short and int) and shouldn't cause trouble, but IMHO those for which multiple different behaviors would be possible should be forbidden outright.Portiere
+1 for mentioning that Integer.equals always returns false if the argument is not an Integer instance.Ekg
F
22

(small) Integer instances are cached, so the invariant x == y is holded for small instances (actually -127 +128, depends on JVM):

Integer a = 10;
Integer b = 10;

assert(a == b); // ok, same instance reused

a = 1024;
b = 1024;

assert(a == b); // fail, not the same instance....
assert(a.equals(b)); // but same _value_

EDIT

4) and 5) yield false because equals check types: X is an Integer whereas Y is a Short. This is the java.lang.Integer#equals method:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }

    return false;
}
Fishworm answered 11/8, 2009 at 11:13 Comment(7)
May i fully complete your answer with that: * and X.equals(y); will box y into ShortRizzo
You are certainly the people's choice here, but you haven't answered the question of why X == y. The results are the same with larger, uncached numbers, as the edited questions shows.Richie
@dfa, the first part of your answer is intriguing me. Can you point me to some docs where this cache is discussed?Pneumonia
@bruno: Integer.valueOf is a good start; then java.sun.com/docs/books/jls/third_edition/html/…Fishworm
Thanks. That helped me a lot. I was looking at a more low level when thins where so simple.Pneumonia
What expression in the question would be true because of the Integer.valueOf() caching? It seems to me this answers a question that was never asked.Vickery
for the edited question what you're saying is true; for the original question (X was in the cached range) this is true :)Fishworm
V
13

The reason for

X == y

being true has to do with binary numeric promotion. When at least one operand to the equality operator is convertible to a numeric type, the numeric equality operator is used. First, the first operand is unboxed. Then, both operands are converted to int.

While

X.equals(y)

is a normal function call. As has been mentioned, y will be autoboxed to a Short object. Integer.equals always returns false if the argument is not an Integer instance. This can be easily seen by inspecting the implementation.

One could argue that this is a design flaw.

Vickery answered 11/8, 2009 at 11:26 Comment(8)
@waxwing: "One could argue that this is a design flaw" ... caused by listening to people who were clamoring for C# features in Java.Ortolan
With my second edit above, I've offered a different example to show how problematic this behavior is.Richie
@Stephen C: I hate to burst your bubble, but that's not entirely fair. To start with, C# has no distinction between a boxed int (aka Integer in Java) and an inline/primitive int. There's no way to have a variable typed as integer reference a boxed integer. The code would return true,true,true,true in C# (int.Equals(int) helps with 4). However, when you'd explicitly use object references, you would get the same results.Underworld
(And no, C# does not have auto-unboxing; unboxing is always explicit in C#.)Underworld
@Ruben: my bubble is not burst! I was not making a statement about C# at all. Rather I was commenting on the people who whined and whinged about not having boxing/unboxing in Java pre 1.5. The Java language designers should have just left well alone.Ortolan
Different situations require different equality semantics. Confusion from difference in == and equals stem mainly from a mistaken belief they should be equivalent. On the other hand, if I had my druthers, the == operator would expressly forbid certain combinations of operands even in cases where one could be implicitly cast to the other. For example, comparisons between a double and either a long or float could be made by casting the double to the other type or casting the other type to double. Which operation is correct would depend upon circumstances.Portiere
As implemented, Java performs the indicated comparisons by casting both operands to double; in some cases, that may be correct behavior, but in other cases it is not. Requiring the program to perform an explicit cast would avoid the ambiguity. Some comparisons only have one sensible behavior (e.g. between short and int) and shouldn't cause trouble, but IMHO those for which multiple different behaviors would be possible should be forbidden outright.Portiere
+1 for mentioning that Integer.equals always returns false if the argument is not an Integer instance.Ekg
S
7

The morale of the story:

Autoboxing/unboxing is confusing, as is type promotion. Together, they make for good riddles but horrendous code.

In practice, it seldom makes sense to use numeric types smaller than int, and I'm almost inclined to configure my eclipse compiler to flag all autoboxing and -unboxing as an error.

Stockman answered 11/8, 2009 at 11:34 Comment(2)
+1: for decrying the horrors of autoboxing. It interracts very nastily with other language features (e.g. varargs, integer promotion) and the "feature" that small integers autobox to the same reference is extremely confusing.Gettings
+1 I still find boxing/unboxing useful, despite the potential for confusion. I try to never use the boxed types for single items, which gets rid of most of the problem. The remaining issues are for example looking for an int in a set of Long values and not finding it.Eager
B
3

Your problem here is not only how it treats == but autoboxing... When you compare Y and 9 you are comparing two primitives that are equal, in the last two cases you get false simply because that's how equals work. Two objects are equal only if they are of the same kind and have the same value. When you say in "X.equals(y)" you are telling it to do Integer.equals(Short) and looking at the implementation of Integer.equals() it will fail:

   public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
    }

Because of autoboxing the last two will result in the same failure as they will both be passed in as Shorts.

Edit: Forgot one thing... In the case of results.add(X == y); it will unbox X and do (X.intValue() == y) which happens to be true as well as 9 == 9

Bate answered 11/8, 2009 at 11:18 Comment(0)
J
1

Java will convert an Integer into an int automatically, if needed. Same applies to Short. This feature is called autoboxing and autounboxing. You can read about it here.

It means that when you run the code:

int a = 5;
Integer b = a;
System.out.println(a == b);

Java converts it into:

int a = 5;
Integer b = new Integer(a);
System.out.println(a == b.valueOf());
Javelin answered 11/8, 2009 at 11:14 Comment(3)
i dont think it auto-unboxes for the == operator, and anyway, the question didnt specify the java version.Skink
@Chii: It didn't need to specify the version; in any version before 5.0, Integer X = 9; wouldn't compile.Kiakiah
it should be Integer b = Integer.valueOf() in order to reuse instancesFishworm
U
1

This automatic conversion is called autoboxing.

Upstroke answered 11/8, 2009 at 11:17 Comment(1)
Actually, it's called "boxing conversion". Autoboxing is a pleonasm. The term autoboxing does not appear in the JLS. That doesn't stop a lot of people from writing it all the time, though.Marzi
H
1

I remember a good practice for overriding "equal(object obj)" is of first checking the type of the parameter passed in. So perhap this causes X.equals(Y) to be false. You might check the souce code to dig out the truth :)

Hagberry answered 11/8, 2009 at 11:23 Comment(1)
Also, it's important generally that if X.equals(Y) is ever true, then Y.equals(X) should also be true, which is why implementations often check that the object is the same exact type.Escapee
P
1

A bit more detail on how autoboxing works and how "small" valued Integer objects are cached:

When a primitive int is autoboxed into an Integer, the compiler does that by replacing the code with a call to Integer.valueOf(...). So, the following:

Integer a = 10;

is replaced by the compiler with the following:

Integer a = Integer.valueOf(10);

The valueOf(...) method of class Integer maintains a cache that contains Integer objects for all values between -127 and 128. If you call valueOf(...) with a value that's in this range, the method returns a pre-existing object from the cache. If the value is outside the range, it returns a new Integer object initialized with the specified value. (If you want to know exactly how it works, lookup the file src.zip in your JDK installation directory, and look for the source code of class java.lang.Integer in it.)

Now, if you do this:

Integer a = 10;
Integer b = 10;
System.out.println(a == b);

you'll see that true is printed - but not because a and b have the same value, but because a and b are referring to the same Integer object, the object from the cache returned by Integer.valueOf(...).

If you change the values:

Integer a = 200;
Integer b = 200;
System.out.println(a == b);

then false is printed, because 200 is outside the range of the cache, and so a and b refer to two distinct Integer objects.

It's unfortunate that == is used for object equality for value types such as the wrapper classes and String in Java - it's counter-intuitive.

Pitman answered 11/8, 2009 at 14:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.