Is Java's equality operator commutative?
Asked Answered
H

3

7

Consider the following Java code:

Integer foo = bar();
if(foo == 5) ...;
if(5 == foo) ...;

Are these comparisons equal -- particularly in the possibility of foo being null? Do they expand to foo.getValue() == 5 and 5 == foo.getValue(), or to something more akin to foo.equals(new Integer(5)) and new Integer(5).equals(foo), or to something else? May one or the other or both or none throw an NPE?

Hesitancy answered 8/2, 2014 at 5:31 Comment(10)
It looks like you're asking whether it is symmetric, not commutative. Normal rules apply when it comes to what throws a NPE (though for what it's worth, I'm pretty sure most implementations return false if the argument is null.)Kamseen
@user2864740: The question is not the same, as I'm also asking of the order of the operands matter.Hesitancy
@Hesitancy That is why I merely linked it. Anyway, see the answer therein - it contains much information.Proxy
@user2864740: Ah, I see; sorry. I thought you were just pointing out a duplicate question.Hesitancy
@DennisMeng: No, this is what an operator being commutative means. I'm fairly sure that an operator * being "symmetric" means that x*x holds true, but I may remember that wrong.Hesitancy
Also related: #1515410Carberry
@Hesitancy Yep, you're remembering it wrong. x*x holding true would make it reflexive.Kamseen
@DennisMeng: No, that is with regards to relations; operators use different terminology. Recall how matrix multiplication is commonly used as an example for something that is not commutative.Hesitancy
Ah wait, yes you're right. Guess I need more caffeine. :)Kamseen
The order of operands does not matter for ==. Whether it matters for .equals() is up to the implementation of .equals(), but best practice would say that a.equals(b) == b.equals(a)Carberry
G
1

From the JLS:

15.21.1. Numerical Equality Operators == and !=

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).

And the relevant rule from 5.1.8 is:

If r is a reference of type Integer, then unboxing conversion converts r into r.intValue()

And 5.6.2 says:

5.6.2. Binary Numeric Promotion

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

If any operand is of a reference type, it is subjected to unboxing conversion (§5.1.8).

Which means that if(foo == 5) ...; means the same as if(foo.intValue() == 5) ...; and if(5 == foo) means if (5 == foo.intValue()). If foo equals null then you will get an NPE in either case.

Grier answered 8/2, 2014 at 5:55 Comment(2)
Thanks! The quotes from the specification make it quite clear what is happening.Hesitancy
It's also worth noting that since Integer is final you can't do something crazy like overriding intValue() to be stateful. :)External
A
1

== is symmetric; that is to say, for any values x and y, (x == y) == (y == x). This is a guarantee provided to us by the JLS §15.21.1 for numbers, and §15.21.3 for reference types (or everything that isn't a primitive value).

It could also be seen as transitive, in that if three values x, y, z exist, and x == y && y == z, then x == z. This is again provided by the same JLS specification - merely repeated to mitigate the issue of the common variable y.

The real problem here comes with regards to autoboxing; when you go to unbox null, then by the JLS, you're going to get a NullPointerException - independent of the comparison operation you're going to do next.

Effectively:

  • You have a boxed primitive type on one side of the comparison, and a primitive on the other. The value of either isn't yet considered.

  • Given that the value of the primitive will force numerical comparison due to it being a boxed primitive, Java will then try to unbox the boxed value.

  • You can't unbox null, hence NullPointerException.

This is (kind of) where equals() steps in - by its contract, two non-null instances must be equivalent to each other if they are indeed the same thing. If either (but not both) of these values are null, then they're not the same instances.

I say "kind of" since there's really nothing to enforce the supposed contract on Object#equals; you could (with some effort) write an asymmetric equals() method, although one would wonder why you would want to.

Aerugo answered 8/2, 2014 at 5:56 Comment(8)
That second property is transitivity, not commutativity.Embow
Actually, symmetric is the term used with regards to binary relations, while commutative is the term for binary operations. == can of course be considered as either, but I considered it primarily as an operation in this case (I thought it reasonable considering I was primarily interested in its side effects, rather than as a relation).Hesitancy
@Dolda2000: Side effects? What side effects? What's outlined by the JLS is merely how it behaves, each time - I couldn't imagine == having any side effects on the operands it receives.Aerugo
@Makoto: It has the side effect of potentially throwing an exception, which is not definable for a relation.Hesitancy
That's not due to ==; that's due to the unboxing of null, which is very well defined, and most definitely not a side effect.Aerugo
But the unboxing of null is an implicit part of the == syntax. And the term "side effect" does not necessarily only carry the meaning it has with regards to functional programming languages. In this case, I use it as having an effect outside of the obvious effect of simply making a boolean comparison.Hesitancy
No...unboxing null doesn't just occur here. It occurs when you need to do anything related to using that boxed instance, including assigning to a primitive value, or perform method operations on it (like intValue()). Lastly, the only way it can make a boolean comparison is clearly defined in §15.21.1, which states that this comparison may perform unboxing conversions. It's not a side effect. It's very much intentional. There's no way you can do a comparison between a wrapper and a primitive without unboxing the value in the wrapper class.Aerugo
You are using a stricter definition of "side effect" than it necessarily has. And I never said that unboxing "only" occurs in this case; I merely said that it does occur here. And the unboxing can be considered a "side effect" because it is outside the obviously "main" effect of doing an equality comparison per se. The terms have meaning outside of Java. But I digress. We can take this to english.stackexchange.com if you wish. ;)Hesitancy
B
0

1) There is no difference between 1 and 2

2) Compiler transforms foo == 5 to foo.intValue() == 5 (outboxing)

3) If foo is null NPE is thrown at runtime

Bouldin answered 8/2, 2014 at 5:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.