-
Notifications
You must be signed in to change notification settings - Fork 0
2019.04.19 Implementing .equals() and .hashCode() in Java
The following example assumes a class called MyClass with a surrogate key called id and two important fields called importantField1 and importantField2:
public boolean equals(Object other) {
if (this == other) {
return true;
}
if ( !(other instanceof MyClass) ) {
return false;
}
// Now it's safe to cast.
final MyClass that = (MyClass) other;
// If both objects have surrogate keys assigned,
// just compare them and be done.
if ( (id != 0) && (that.id != 0) ) {
return (id == that.id);
}
// Otherwise compare the *same* important fields that you use
// in .hashCode().
return Objects.equals(importantField1, that.importantField1) &&
Objects.equals(importantField2, that.importantField2);
}
@Override
public int hashCode() {
if (id == 0) {
// If there's no surrogate key assigned, hash the same important fields
// you use in .equals()
return Arrays.hashCode(new Object[] { importantField1, importantField2 });
} else {
// Have a surrogate key? Just return the low bits.
return (int) id;
}
}Notice:
- The surrogate key is usually assigned by the database the first time the object is saved (after object creation, so it's not part of the constructor). If the surrogate key has been assigned, it will be used as the only significant check for hashCode and equality.
- Otherwise, we compare the same important fields in
.equals()as we use to create the.hashCode(). - Not every field is important. Good indicators of importance is if fields are non-null and/or required by the constructor. We generally only care about the minimum number of fields that uniquely identify this object when the surrogate key (id) has not been set.
You could write something like:
int ret = 0;
if (importantField1 != null) {
ret += importantField1;
}
if (importantField2 != null) {
ret += importantField2;
}
return ret;That would be correct. It might even be marginally faster because no array is created and no function is called. But I would start using Arrays.hashCode() because:
- It's easy to see if you're comparing the same fields as in your equals method.
- It's really hard to make any kind of typo.
- It creates a strong hashCode. Unless you know your data really well and want to do some funky bitfield manipulation that your coworkers won't understand, and you can prove that you're doing more good than harm (by using TestUtils), Arrays.hashCode() is probably going to do the right thing.
- Enough stuff uses this method that the JVM should already have it cached or inlined or whatever.
- If it turns out to be a few percent slower for some reason, you can always write it out long-hand, but 99% of the time, it's good enough.
if (this == other) statement compares two objects because it's incredibly cheap to compare memory locations and if they are equal, you're done after doing almost no work.
if ( !(other instanceof MyClass) ) is a fairly cheap way to exclude anything that could not possibly be equal. It also excludes the case where other is null. Again, this is a shortcut to skip any unnecessary work.
You really want to use the name of your class in this instanceof comparison. You could technically use the name of an interface, but it's usually a bad idea. They did that with HashMap and SortedMap inheriting .equals() from AbstractMap. This means that sort-order is ignored for equality. Is that what you want? In some cases, yes. In others, no. Any time you implement .equals() on a non-final class you are asking for this kind of ambiguity and confusion, so try not to do that!
Having determined the class, you can safely cast to it final MyClass that = (MyClass) other; so that we can use that instead of other for all subsequent comparisons.
Objects.equals() compares two objects taking into account whether they are null or not, then calling their .equals() methods if they are both non-null. It returns true if both are null.
A: Because:
- Having good equals methods makes unit testing much easier. If you make .toString() print out the same fields, even better.
- You need these to use any hash-based collection such as HashMap or HashSet.
A: That's how most tuples work and a lot of other things. For your own code it is generally sufficient or even preferable to only compare the fields that force the object to be unique. At least start there, until you find a reason to do differently.
A: You probably want to compare the same fields you use in your .equals() and .hashCode() methods. Only .equals() and .hashCode() technically have to be compatible, but it's really surprising to use different sets of fields for different kinds of comparisons. If you must do that, implement a separate Comparator to make it clear.
A: Simply leave out the id field and related ifs.
A: Awesome! It's a good idea to test any variations with something like TestUtils. If it works, please share!