Equality – Part 2
September 18th, 2008
In my previous post about equality, I ended by saying there were issues, or at very least assumptions, with the proposed equals method in the Person class. Below is the previous incarnation of the equals method:
public boolean equals(Object other) {
Person otherPerson = (Person)other;
return this.getName().equals(otherPerson.getName());
}
One issue introduces an implicit assumption in this code. In the second line, if the current Person object’s name attribute (i.e. this.getName())is null, this line will throw a NullPointerException as it will attempt to call the equals method on an object that isn’t there. Depending on the implementation of the String class’ equals method, which is what is being called since name is a String, it may also throw that exception if the otherPerson name variable is null.
This may not be an issue depending on the rest of the code. If the setter for name ensures that it will never be null, then this implementation is not only correct, but also the simplest solution. A typical approach to ensuring this is the case is as follows:
1 2 3 4 5 | public void setName(String name) { if (name == null) throw new IllegalArgumentException("Name cannot be null"); this.name = name; } |
Remember that when an exception is thrown, the method immediately stops executing (note: there are ways to avoid this, but are outside the scope of this post), therefore the actual name assignment will never be reached if the name is null.
If the constructor were to take the name as a parameter, it is important to use this setter in the constructor in order to utilize this validation code as well:
Good:
1 2 3 | public Person(String name) { setName(name); } |
Not so good, since it duplicates the validation logic:
1 2 3 4 5 | public Person(String name) { if (name == null) throw new IllegalArgumentException("Name cannot be null"); this.name = name; } |
Bad:
1 2 3 | public Person(String name) { this.name = name; } |
Getting back to the original point, if we have ensured that name will not be null, the above implementation works. Otherwise, we will have to only execute that portion of the equals method if the name is not null:
1 2 3 4 5 6 7 | public boolean equals(Object other) { Person otherPerson = (Person)other; if (this.getName() != null && otherPerson.getName() != null) { return this.getName().equals(otherPerson.getName()); else return this == other; } |
In the above code, I defaulted to simple object reference comparison in case we cannot check names.
The second potential issue with the implementation revolves around the blind cast to a Person object in the first line. Remember we can check to ensure a case will be successful before actually performing it:
1 2 | if (other instanceof Person) Person otherPerson = (Person) other; |
The instanceof operator will only return true if the cast will be successful. Therefore, the above code is guaranteed to not throw an exception (for the record, when an illegal cast is attempted a ClassCastException is thrown).
The Java API is not specific on how to handle such a situation in the equals method. Refer back to part 1 of the equality series or the Object class API for details on what equals implementations must honor. Therefore we have two options:
- If the cast will be invalid, we cannot properly test for equality. In other words, we’re comparing apples and oranges. Some implementations will simply return
falsein this case. Logically, this makes sense; if we cannot cast them to the same type, they are clearly not equal. - Some developers do not bother checking the cast and will let the
ClassCastExceptionbubble up. The rationale is that if a developer should not be attempting to compare two unalike objects in the first place, which does represent an error condition.
Since the Java API does not define how to handle the situation in the contract for equals, the developer is free to choose whichever approach they prefer. Personally, I tend towards returning false in situations of comparing different object types.
It should also be noted that many IDEs can generate the equals method for you. In IntelliJ, for instance, it will prompt the user to select two things:
- For each attribute in the class, select which should be used in the equality comparison. In keeping with the person example, I would select name but not address, since the address likely wouldn’t play into the identification of a person.
- For each attribute to be included in the equals method, it will then ask which are guaranteed to not be
null. This will let IntelliJ save unnecessarynullchecks for fields we can guarantee will not need them. Again, we need to be careful in our setters to ensure they will not benull.
This is a pretty useful feature of IDEs and can save quite a bit of time when developing a domain model.
Read the first part of this series:


Howard Swope
September 23rd, 2008 at 8:39 am
I was curious about your thoughts, or any java standards, about equality as it relates to deep copy vs. shallow copy.
I am guessing one usually defaults to the .Equals of any internal object int determining equality of the containing object, but it is really up to the object itself to determin if reference equality or state equality is pertinent to its identity.
Professor Jay
September 23rd, 2008 at 2:01 pm
Exactly. The equals() method of an object is an aggregate of its relevant members’ equals() methods. In nearly all cases, you finally end up doing a primitive comparison or using the String equals() implementation.
As you said, it’s up to the object, but I don’t think I’ve ever seen a case where the object reference has been checked. I suppose before Java formally supported enums, this might have been used when comparing objects from a known pool.