What Issues Should Be Considered When Overriding Equals and Hashcode in Java

What issues should be considered when overriding equals and hashCode in Java?

The theory (for the language lawyers and the mathematically inclined):

equals() (javadoc) must define an equivalence relation (it must be reflexive, symmetric, and transitive). In addition, it must be consistent (if the objects are not modified, then it must keep returning the same value). Furthermore, o.equals(null) must always return false.

hashCode() (javadoc) must also be consistent (if the object is not modified in terms of equals(), it must keep returning the same value).

The relation between the two methods is:

Whenever a.equals(b), then a.hashCode() must be same as b.hashCode().

In practice:

If you override one, then you should override the other.

Use the same set of fields that you use to compute equals() to compute hashCode().

Use the excellent helper classes EqualsBuilder and HashCodeBuilder from the Apache Commons Lang library. An example:

public class Person {
private String name;
private int age;
// ...

@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;

Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}

Also remember:

When using a hash-based Collection or Map such as HashSet, LinkedHashSet, HashMap, Hashtable, or WeakHashMap, make sure that the hashCode() of the key objects that you put into the collection never changes while the object is in the collection. The bulletproof way to ensure this is to make your keys immutable, which has also other benefits.

Why do I need to override the equals and hashCode methods in Java?

Joshua Bloch says on Effective Java

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

Let's try to understand it with an example of what would happen if we override equals() without overriding hashCode() and attempt to use a Map.

Say we have a class like this and that two objects of MyClass are equal if their importantField is equal (with hashCode() and equals() generated by eclipse)

public class MyClass {
private final String importantField;
private final String anotherField;

public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}

@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}

Imagine you have this

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Override only equals

If only equals is overriden, then when you call myMap.put(first,someValue) first will hash to some bucket and when you call myMap.put(second,someOtherValue) it will hash to some other bucket (as they have a different hashCode). So, although they are equal, as they don't hash to the same bucket, the map can't realize it and both of them stay in the map.


Although it is not necessary to override equals() if we override hashCode(), let's see what would happen in this particular case where we know that two objects of MyClass are equal if their importantField is equal but we do not override equals().

Override only hashCode

If you only override hashCode then when you call myMap.put(first,someValue) it takes first, calculates its hashCode and stores it in a given bucket. Then when you call myMap.put(second,someOtherValue) it should replace first with second as per the Map Documentation because they are equal (according to the business requirement).

But the problem is that equals was not redefined, so when the map hashes second and iterates through the bucket looking if there is an object k such that second.equals(k) is true it won't find any as second.equals(first) will be false.

Hope it was clear

Should I be overriding equals and hashCode in child classes even if it's not adding anything?

This is a bit complicated; I have to explain a few things about how equals and hashCode works to explain viable solutions.

There is a 'contract'. The compiler cannot enforce it, but if you do not adhere to this contract, weird things will happen. Specifically: Your objects will just do the wrong thing when used as keys in hashmaps, and possibly other such problems when using third party libraries. To properly adhere to the contract, any given class either needs to opt out of equals/hashCode entirely, OR, the entire chain (so, the class and all its subclasses) need to properly override hashCode and equals, except, you really can't do that unless the parent is properly instrumented to do so.

The contract states that this must always be correct:

  • a.equals(b) -> b.equals(a).
  • a.equals(b) and b.equals(c) -> a.equals(c).
  • a.equals(a).
  • a.equals(b) -> a.hashCode() == b.hashCode(). (note, the reverse need not be true; equal hashcodes does not imply the objects are equal).

The contract is REALLY difficult to guarantee in the face of a class hierarchy! Imagine that we take the existing java.util.ArrayList and subclass it with the notion of 'color'. So now we can have a blue ColoredArrayList, or a red ColoredArrayList. It would make perfect sense to say that a blue ColoredArrayList definitely should NOT equal a red ColoredArrayList, except.. the equals impl of ArrayList itself (which you cannot change), effectively defines that you simply cannot extend ArrayList with properties like this at all: if you call a.equals(b) where a is an empty arraylist and b is some empty List (say, an empty red ColoredArrayList), it'll just check equality of each member in it, which, given that they are both empty, is trivially true. So, the empty normal arraylist is equal to both the empty red and the empty blue ColoredArrayList, and therefore the contract stipulated that an empty red ColoredArrayList must equal an empty blue ColoredArrayList. In that sense, sonar is just broken here. There's a problem, and it is unfixable. It is impossible to write the concept of ColoredArrayList in java.

Nevertheless, there is a solution, but only if every class in the hierarchy is on board. This is the canEqual approach. The way out of the colored dilemma as above is to differentiate the notion of 'I am extending, and adding new properties' and 'I am extending, but, these things are still semantically speaking the exact same thing with no new properties'. ColoredArrayList is the former case: It's an extension that adds new properties. The idea of canEqual is that you create a separate method to indicate this, which lets ArrayList figure out: I cannot be equal to ANY ColoredArrayList instance, even if all elements are the same. Then we can adhere to the contract again. ArrayList does NOT have this system in place and therefore, given that you cannot change ArrayList's source code, you're stuck: It is not fixable. But if you write your own class hierarchy, you can add it.

Project Lombok takes care of adding equals and hashCode for you. Even if you don't want to use it, you can look at what it generates and duplicate this in your own code. This will also remove the warnings that sonar emits. See https://projectlombok.org/features/EqualsAndHashCode – this also shows you how the canEqual concept can be used to avoid the ColoredArrayList dilemma.

Here you subclass without adding new properties, so, there's no actual need to replace hashCode and equals. But sonar doesn't know that.

When do you need to override hashcode() and equals() when using a hashmap

The answer is yes.

In Java you can add objects in collections. Let us say you wanted to find an object called A that you added to a list called L. Let us say this is an object that you defined with your own class and you override the method Object#equals(). When you are looping through list L you are testing if any of these objects are equal to object A. If the equals method returns true you have found your object.

When you add objects to any HashTable, HashMap or HashSet the hashcode method is used to generate a number. That number should as unique as possible. It is possible that objects of the same class have different values in their instance fields but their hashcode method produces the same value. If you have two objects X and Y of some class and they have the same hashcode and you put both of them in a HashMap Q they end up in the same bucket P. Let us say that P has two objects. Let say that you pass Q into a method with X and Y. The method wants to check if X exists in Q. Q will take X and get the hashcode. Q will use the hashcode to find a bucket. The bucket will be P. Bucket P has two objects. This is when the equals method is used to determine if the Bucket contains X by comparing each object in the bucket with X. If one of the object in the bucket matches X then X exists in Q.

Why should I override hashCode() when I override equals() method?

It works for you because your code does not use any functionality (HashMap, HashTable) which needs the hashCode() API.

However, you don't know whether your class (presumably not written as a one-off) will be later called in a code that does indeed use its objects as hash key, in which case things will be affected.

As per the documentation for Object class:

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

Problem with overriding equals() and hashCode() for a List of Double

This looks like an XY problem.

You are entangled in a non-trivial transitivity issue associated with overriding equals() due to your business rule ("if the Doubles are close with respect to some epsilon..."), as pointed out in a comment to the OP.

In fact it is impossible for you to meaningfully implement an equals() method for that rule, because transitivity ("if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true") cannot be guaranteed. You might have three Data objects, d1, d2 and d3, where d2 was equal (i.e. close enough) to both d1 and d3, but d1 was not equal (i.e. not close enough) to d3.

There is nothing wrong with the rules Java imposes when testing for equality, and there is nothing wrong with your specific condition for determining the equality of your Data instances either. It's just that they are incompatible.

But while there are a bunch of rules you should definitely follow if you go down the equals() path, I don't see anything in your question that indicates you have to override anything. So don't go there.

Why not just create a new method public boolean sameAs(Object other) in the Data class? It can check for equality based on your rule(s), and your unit tests can call that method. Then you have no need or obligation to implement equals() and hashCode() at all.

(Updated on 9/12/19 to clarify why equals() cannot be implemented.)

what is the disadvantage of overriding equals and not hashcode and vice versa?

Yes it's correct when you override equals method you have to override hashcode method as well. The reason behind is that in hash base elements two objects are equal if their equals method return true and their hashcode method return same integer value. In hash base elements (hash map) when you make the equal check for two objects first their hashcode method is get called, if it return same value for both then only equals method is get called. If hashcode don't return same value for both then it simplity consider both objects as not equal. By default the hashcode method return some random value, so if you are making two objects equal for some specific condition by overriding equals method, they still won't equal because their hashcode value is different, so in order to make their hascode value equal you have to override it. Otherwise you won't be able to make this object as a key to your hash map.

When do I need to override equals and hashcode methods?

If I compare 2 instances of A without override the equals method, will I get expected result?

That depends on what you expect :)

The default implementation will give you reference equality - in other words, when you compare two references, equals will only return true if they're references to the same object.

You would normally override equals to implement "value equality" - where two distinct objects are deemed equal, usually by virtue of having equal field values themselves. The exact meaning of equality will depend on your design - the two objects could still be distinguishable in other ways, for example.

If you override equals, you should also override hashCode to be consistent with equals, such that if a.equals(b) is true, then a.hashCode() == b.hashCode(). This will allow instances of your class to be used as keys in hash-based collections (e.g. HashMap) so that you can look up a value based on a key which is equal to the original one, rather than having to use a reference to the exact original key object.



Related Topics



Leave a reply



Submit