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?

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
  • 1
    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`.) – Dennis Meng Feb 08 '14 at 05:36
  • @user2864740: The question is not the same, as I'm also asking of the order of the operands matter. – Dolda2000 Feb 08 '14 at 05:36
  • @Dolda2000 That is why I merely linked it. Anyway, [*see the answer*](http://stackoverflow.com/a/3352798/2864740) therein - it contains much information. – user2864740 Feb 08 '14 at 05:37
  • @user2864740: Ah, I see; sorry. I thought you were just pointing out a duplicate question. – Dolda2000 Feb 08 '14 at 05:38
  • @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. – Dolda2000 Feb 08 '14 at 05:42
  • Also related: https://stackoverflow.com/questions/1514910/when-comparing-two-integers-in-java-does-auto-unboxing-occur – keshlam Feb 08 '14 at 05:44
  • @Dolda2000 Yep, you're remembering it wrong. `x*x` holding true would make it *reflexive*. – Dennis Meng Feb 08 '14 at 05:45
  • @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. – Dolda2000 Feb 08 '14 at 05:46
  • 1
    Ah wait, yes you're right. Guess I need more caffeine. :) – Dennis Meng Feb 08 '14 at 05:48
  • 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)` – keshlam Feb 08 '14 at 05:48

3 Answers3

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.

Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • 1
    Thanks! The quotes from the specification make it quite clear what is happening. – Dolda2000 Feb 08 '14 at 06:03
  • It's also worth noting that since Integer is final you can't do something crazy like overriding intValue() to be stateful. :) – Rag Feb 08 '14 at 06:16
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.

Makoto
  • 104,088
  • 27
  • 192
  • 230
  • That second property is transitivity, not commutativity. – Chris Hayes Feb 08 '14 at 05:58
  • 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). – Dolda2000 Feb 08 '14 at 06:01
  • @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. – Makoto Feb 08 '14 at 06:03
  • @Makoto: It has the side effect of potentially throwing an exception, which is not definable for a relation. – Dolda2000 Feb 08 '14 at 06:04
  • 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. – Makoto Feb 08 '14 at 06:05
  • 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. – Dolda2000 Feb 08 '14 at 06:06
  • 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. – Makoto Feb 08 '14 at 06:12
  • 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. ;) – Dolda2000 Feb 08 '14 at 06:17
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

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275