-
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 (this.getId() == 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) this.getId();
}
}Notice:
- If the surrogate key has been assigned, it will be used as the only significant check for hashCode and equality.
- Otherwise, it will compare the same important fields in
.equals()as it uses to create the.hashCode(). - Not every field is important. Good indicators of importance is fields are non-null and required by the constructor. But really, we 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 other 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, Arrays.hashCode() is probably going to do the right thing.
- A lot of built-in stuff uses that so 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 it saves a lot of 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.
You really want to use the name of your class in this comparison. Yes, 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: 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 fields for one kind of comparison than for another. If you must do that, implement a separate Comparator to make it clear.