77

The contract of equals with regards to null, is as follows:

For any non-null reference value x, x.equals(null) should return false.

This is rather peculiar, because if o1 != null and o2 == null, then we have:

o1.equals(o2) // returns false
o2.equals(o1) // throws NullPointerException

The fact that o2.equals(o1) throws NullPointerException is a good thing, because it alerts us of programmer error. And yet, that error would not be catched if for various reasons we just switched it around to o1.equals(o2), which would just "silently fail" instead.

So the questions are:

  • Why is it a good idea that o1.equals(o2) should return false instead of throwing NullPointerException?
  • Would it be a bad idea if wherever possible we rewrite the contract so that anyObject.equals(null) always throw NullPointerException instead?

On comparison with Comparable

In contrast, this is what the Comparable contract says:

Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

If NullPointerException is appropriate for compareTo, why isn't it for equals?

Related questions


A purely semantical argument

These are the actual words in the Object.equals(Object obj) documentation:

Indicates whether some other object is "equal to" this one.

And what is an object?

JLS 4.3.1 Objects

An object is a class instance or an array.

The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

My argument from this angle is really simple.

  • equals tests whether some other object is "equal to" this
  • null reference gives no other object for the test
  • Therefore, equals(null) should throw NullPointerException
Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • 2
    commenting here that it is known for a fact that, in Java, with *equals()* present at the very top of the OO hierarchy, it is *impossible* to respect the equals contract for anything but the simplest case (ie when you're not doing OO at all). Thinking that there is such a thing as a non-broken Java *equals()* contract is delusional. We go much further: by default *equals()* and *hashCode()* are throwing UOE. If you want to use these methods, you *must* document how you're dealing with the fundamental issues raised here: http://www.artima.com/lejava/articles/equality.html – SyntaxT3rr0r May 22 '10 at 13:55
  • 1
    8 votes and 3 favorites on my question related to the indisputable brokenness of *equals* here: http://stackoverflow.com/questions/2205565 The thing is: the "common equals wisdom" simply doesn't work. Not only people like Joshua Bloch an Martin Odersky are saying it but you can use logic to prove that fact. You simply cannot do an OOA/OOD to OOP translation and hope to reuse the Java concept of equality: to me it is a fundamental flaw in the language that *equals* is present in Object. Of course people drinking the Gosling kool-aid will disagree. Let them argue with Bloch – SyntaxT3rr0r May 22 '10 at 14:00
  • 1
    my final point is this: in many case it's not about throwing an NPE or returning false: it's about throwing a huge big *UnsupportedOperationException* and it is a Java flaw to allow to call *equals* on objects that shouldn't have that very concept of equality in the first place. Famous last word: *UnsupportedOperationException* :) – SyntaxT3rr0r May 22 '10 at 14:02

12 Answers12

108

To the question of whether this asymmetry is inconsistent, I think not, and I refer you to this ancient Zen kōan:

  • Ask any man if he's as good as the next man and each will say yes.
  • Ask any man if he's as good as nobody and each will say no.
  • Ask nobody if it's as good as any man and you'll never get a reply.

At that moment, the compiler reached enlightenment.

Sean Owen
  • 66,182
  • 23
  • 141
  • 173
19

An exception really should be an exceptional situation. A null pointer might not be a programmer error.

You quoted the existing contract. If you decide to go against convention, after all this time, when every Java developer expects equals to return false, you'll be doing something unexpected and unwelcome that will make your class a pariah.

I could't disagree more. I would not rewrite equals to throw an exception all the time. I'd replace any class that did that if I were its client.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • I like the reasoning in the question (you can't use a variable if it is `null`) - but @duffymo is right and the support for his answer comes from how Java code itself implements `.equals()` for many classes (check in the source code). Equality is first checked with == and then inequality is checked using instanceOf. By definition both of those checks will return a boolean without `NullPointerException` - i.e. the given object is not used within the `.equals()` method until it has been proven to be non-`null`. So, whenever possible you should write your own classes to behave the same way. – PMorganCA Oct 29 '14 at 18:40
8

Think of how .equals is related to == and .compareTo is related to the comparison operators >, <, >=, <=.

If you're going to argue that using .equals to compare an object to null should throw a NPE, then you'd have to say that this code should throw one as well:

Object o1 = new Object();
Object o2 = null;
boolean b = (o1 == o2); // should throw NPE here!

The difference between o1.equals(o2) and o2.equals(o1) is that in the first case you're comparing something to null, similar to o1 == o2, while in the second case, the equals method is never actually executed so there's no comparison happening at all.

Regarding the .compareTo contract, comparing a non-null object with a null object is like trying do this:

int j = 0;
if(j > null) { 
   ... 
}

Obviously this won't compile. You can use auto-unboxing to make it compile, but you get a NPE when you do the comparison, which is consistent with the .compareTo contract:

Integer i = null;
int j = 0;
if(j > i) { // NPE
   ... 
}
Angus
  • 121
  • 2
  • I would argue that there is a fundamental difference between the equality (==) and relational (<, >, <=, >=) operators when it comes to objects. The relational objects do not take objects as operands (even if they are of the same type) so the compareTo method is used instead. On the other hand, the equals method is not a substitute for the equality operator. The equality operator can be still be used on objects (given they are of the same type) and serves a different purpose than the equals method. – Shazz Oct 16 '16 at 05:53
4

If you take object oriented concepts into account, and consider the whole sender and receiver roles, I'd say that behaviour is convenient. See in the first case you're asking an object if he is equal to nobody. He SHOULD say "NO, I'm not".

In the second case though, you don't have a reference to anyone So you aren't really asking anyone. THIS should throw an exception, the first case shouldn't.

I think it's only asymmetric if you kind of forget about object orientation and treat the expression as a mathematical equality. However, in this paradigm both ends play different roles, so it is to be expected that order matters.

As one final point. A null pointer exception should be raised when there's an error in your code. However, Asking an object if he is nobody, shouldn't be considered a programming flaw. I think it's perfectly ok to ask an object if he isn't null. What if you don't control the source that provides you with the object? and this source sends you null. Would you check if the object is null and only afterwards see if they are equals? Wouldn't it be more intuitive to just compare the two and whatever the second object is the comparison will be carried out without exceptions?

In all honesty, I would be pissed if an equals method within its body returns a null pointer exception on purpose. Equals is meant to be used against any sort of object, so it shouldn't be so picky on what it receives. If an equals method returned npe, the last thing on my mind would be that it did that on purpose. Specially considering it's an unchecked exception. IF you did raise an npe a guy would have to remember to always check for null before calling your method, or even worse, surround the call to equals in a try/catch block (God I hate try/catch blocks) But oh well...

arg20
  • 4,893
  • 1
  • 50
  • 74
  • 1
    The question "Are you equal to nobody" is somewhat dubious. On the other hand, `equals` isn't given an *object*--it's given an object *reference*. The question is really "Does this reference identify an object which is equal to you". Such a question can easily be answered "No--since that reference doesn't identify any object, it certainly doesn't identify an object that's equivalent to me". – supercat Oct 15 '13 at 18:01
  • Note further that the reformulated question justifies equality comparisons involving any combination of object types. Someone who is asked "Are you equal to the color red" might regard the question as malformed, but "Does this reference [which identifies the color red] refer to an object which is equivalent to you" is not. The reference identifies something that is not equivalent to the person, so the answer is "no". – supercat Oct 15 '13 at 18:03
  • I understand the difference you made, and I agree with it. In practice though, you said what I'm saying only with more accurate and technical terms. If you get the opposite idea from my answer then perhaps I've expressed myself poorly. – arg20 Oct 15 '13 at 23:33
  • You expressed yourself pretty well; my point was to suggest an unambiguous rephrasing of the question which would clearly have no problem even with a null reference. It's too bad there's no concise notation for performing an "equals" check on a pair of objects, either or both of which might be null. – supercat Oct 16 '13 at 14:58
  • One thing I'd like to see in a modern object-oriented language, btw, would be a means of declaratively or syntactically distinguishing cases where a reference encapsulates an object's *identity*, the various cases where it encapsulates a *values* (with the ability to distinguish different combinations of shared vs unshared, mutable vs read-only vs immutable, and safe-to-mutate vs do-not-disturb), and the occasional case where the reference should be an entity unto itself. Java is very weak in this regard, and unfortunately .NET borrows much of its weakness. – supercat Oct 16 '13 at 15:27
4

Not that this is neccessarily an answer to your question, it is just an example of when I find it useful that the behaviour is how it is now.

private static final String CONSTANT_STRING = "Some value";
String text = getText();  // Whatever getText() might be, possibly returning null.

As it stands I can do.

if (CONSTANT_STRING.equals(text)) {
    // do something.
}

And I have no chance of getting a NullPointerException. If it were changed as you suggested, I would be back to having to do:

if (text != null && text.equals(CONSTANT_STRING)) {
    // do something.
}

Is this a good enough reason for the behaviour to be as it is?? I don't know, but it is a useful side-effect.

DaveJohnston
  • 10,031
  • 10
  • 54
  • 83
2

Personally, I'd rather it perform as it does.

The NullPointerException identifies that the problem is in the object against which the equals operation is being performed.

If the NullPointerException was used as you suggest and you tried the (sort of pointless) operation of...

o1.equals(o1) where o1= null... Is the NullPointerException thrown because your comparison function is screwed or because o1 is null but you didn't realise? An extreme example, I know, but with current behaviour I feel you can tell easily where the problem lies.

Rob L
  • 214
  • 2
  • 3
2

In the first case o1.equals(o2) returns false because o1 is not equal to o2, which is perfectly fine. In the second case, it throws NullPointerException because o2 is null. One cannot call any method on a null. It may be a limitation of programming languages in general, but we have to live with it.

It is also not a good idea to throw NullPointerException you are violating the contract for the equals method and making things more complex than it has to be.

fastcodejava
  • 39,895
  • 28
  • 133
  • 186
2

There are many common situations where null is not in any way exceptional, e.g. it may simply represent the (non-exceptional) case where a key has no value, or otherwise stand for “nothing”. Hence, doing x.equals(y) with an unknown y is also quite common, and having to always check for null first would be just wasted effort.

As for why null.equals(y) is different, it is a programming error to call any instance method on a null reference in Java, and therefore worthy of an exception. The ordering of x and y in x.equals(y) should be chosen such that x is known to not be null. I would argue that in almost all cases this reordering can be done based on what is known about the objects beforehand (e.g., from their origin, or by checking against null for other method calls).

Meanwhile if both objects are of unknown “nullness”, then other code almost certainly requires checking at least one of them, or not much can be done with the object without risking the NullPointerException.

And since this is the way it is specified, it is a programming error to break the contract and raise an exception for a null argument to equals. And if you consider the alternative of requiring an exception to be thrown, then every implementation of equals would have to make a special case of it, and every call to equals with any potentially null object would have to check before calling.

It could have been specified differently (i.e., the precondition of equals would require the argument to be non-null), so this is not to say that your argumentation is invalid, but the current specification makes for a simpler and more practical programming language.

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • You can actually call static methods on a null reference, so your answer is not entirely accurate. It should go without saying that this is a heinously bad thing to do though. – CurtainDog Aug 05 '13 at 01:33
1

I think it's about convenience and more importantly consistency - allowing nulls to be part of the comparison avoids having to do a null check and implement the semantics of that each time equals is called. null references are legal in many collection types, so it makes sense they can appear as the right side of the comparison.

Using instance methods for equality, comparison etc., necessarily makes the arrangement asymmetric - a little hassle for the huge gain of polymorphism. When I don't need polymorphism, I sometimes create a symmetric static method with two arguments, MyObject.equals(MyObjecta, MyObject b). This method then checks whether one or both arguments are null references. If I specifically want to exclude null references, then I create an additional method e.g. equalsStrict() or similar, that does a null check before delegating to the other method.

mdma
  • 56,943
  • 12
  • 94
  • 128
1

Note that the contract is "for any non-null reference x". So the implementation will look like:

if (x != null) {
    if (x.equals(null)) {
        return false;
    }
}

x need not be null to be deemed equal to null because the following definition of equals is possible:

public boolean equals(Object obj) {
    // ...
    // If someMember is 0 this object is considered as equal to null.
    if (this.someMember == 0 and obj == null) {
         return true;
    }
    return false;
}
Vijay Mathew
  • 26,737
  • 4
  • 62
  • 93
1

You should return false if the parameter is null.

To show that this is the standard, see 'Objects.equals(Object, Object) from java.util, that performs an assymetric null check on the first parameter only (on which equals(Object) will be called). From the OpenJDK SE 11 source code (SE 1.7 contains exactly the same):

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

This also handles two null values as equal.

Dávid Horváth
  • 4,050
  • 1
  • 20
  • 34
0

This is a tricky question. For backward compatability you can't do so.

Imagine the following scenario

void m (Object o) {
 if (one.equals (o)) {}
 else if (two.equals (o)) {}
 else {}
}

Now with equals returning false else clause will get executed, but not when throwing an exception.

Also null is not really equal to say "2" so it makes perfect sense to return false. Then it is probably better to insist null.equals("b") to return also false :))

But this requirement does make a strange and non symmetric equals relation.

Anton
  • 2,653
  • 1
  • 16
  • 7