Here is an example of a Person
class that I will use as a key.
It overrides equals()
so that it always returns false
and hashCode()
that always returns 1
.
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
System.out.println("Equals is called");
return false;
}
@Override
public int hashCode() {
System.out.println("Hashcode is called");
return 1;
}
}
Now let's use it as a key and randomly fill the map.
public class Main {
public static void main(String[] args) {
var person1 = new Person("Mike", 21);
var person2 = new Person("Alex", 32);
var person3 = new Person("Andrew", 45);
Map<Person, String> peopleMap = new HashMap<>();
peopleMap.put(person3, "SomeValue3");
peopleMap.put(person2, "SomeValue1");
peopleMap.put(person1, "SomeValue2");
System.out.println("Getting a person \n \n");
System.out.println(peopleMap.get(person3));
}
}
Here is what I see in a console:
Hashcode is called
Hashcode is called
Equals is called
Hashcode is called
Equals is called
Equals is called
Getting a person
Hashcode is called
SomeValue3
Questions:
How could it determine the right key if the equals is always false
?
How could it get the right key without even calling equals()
for getting an object by the key?
If we move adding the person3 object down ( the one we are getting ) so it is in the middle - 1
equals()
will be called for the first object. If we move adding to the 3 position, like this:
peopleMap.put(person2, "SomeValue1");
peopleMap.put(person1, "SomeValue2");
peopleMap.put(person3, "SomeValue3");
2
times equals()
will be called ( for person2
and person1
) but none for the key we are getting.
Why is it so? I always thought that map can't store an order, but look like this.
Your equals
implementation violates the contract of the method, specifically the first clause in the javadoc:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
However the HashMap
implementation assumes the condition holds and as an optimization will first check if the key object is the same by reference before using equals
. You can see this when you look at the source code of HashMap
.
final Node<K,V> getNode(Object key) {
...
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
...
You can also confirm this by stepping through the execution with a debugger.