Why are these == but not `equals()`?

2019-01-08 20:25发布

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.

标签: java equals
8条回答
神经病院院长
2楼-- · 2019-01-08 21:02

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.

查看更多
SAY GOODBYE
3楼-- · 2019-01-08 21:11

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

查看更多
可以哭但决不认输i
4楼-- · 2019-01-08 21:12

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.

查看更多
萌系小妹纸
5楼-- · 2019-01-08 21:17

This automatic conversion is called autoboxing.

查看更多
爷、活的狠高调
6楼-- · 2019-01-08 21:17

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.

查看更多
Rolldiameter
7楼-- · 2019-01-08 21:17

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());
查看更多
登录 后发表回答