in HashMap when I pass List of Objects as Key I get different results.
List<NewClass> list1 = new ArrayList<>();
List<NewClass> list2 = new ArrayList<>();
NewClass obj1 = new NewClass(1, "ddd", "[email protected]");
NewClass obj2 = new NewClass(2, "ccc", "[email protected]");
list1.add(obj1);
list1.add(obj2);
list2.add(obj1);
list2.add(obj2);
Map<List<NewClass>, Integer> mapClass = new HashMap<>();
mapClass.put(list1, 1234);
mapClass.put(list2, 4567);
System.out.println(mapClass.size());
System.out.println(mapClass.get(list1));
NewClass obj4 = new NewClass(1, "ddd", "[email protected]");
NewClass obj5 = new NewClass(2, "ccc", "[email protected]");
List<NewClass> list3 = new ArrayList<>();
list3.add(obj4);
list3.add(obj5);
System.out.println(mapClass.get(list3));
System.out.println(list1.hashCode());
System.out.println(list2.hashCode());
System.out.println(list3.hashCode());
Below is the output I see
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
1
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
4567
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
**null**
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
-1879206775
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
-1879206775
hashCode() called - Computed hash: -1704251796
hashCode() called - Computed hash: -587009612
-1879206775
Even though hashcode is same for all the 3 lists, mapClass.get(list3) is retuning null. list3 has same object as list1 / list2. Why is this behaviour ?
CodePudding user response:
From map V get(Object key)
documentation:
* ... if this map contains a mapping from a key
* {@code k} to a value {@code v} such that
* {@code Objects.equals(key, k)},
* then this method returns {@code v}; otherwise
* it returns {@code null}. ...
I'm not sure how you implemented the equals
method of NewClass
, but the following implementation of NewClass
doesn't return null
when calling System.out.println(mapClass.get(list3))
;
public class NewClass {
private int id;
private String name;
private String mail;
NewClass(int id,String name,String mail){
this.id = id;
this.name = name;
this.mail = mail;
}
@Override
public int hashCode() {
return id * name.hashCode() * mail.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof NewClass)) {
return false;
}
NewClass newClass = (NewClass) o;
return newClass.id == id &&
newClass.name.equals(name) &&
newClass.mail.equals(mail);
}
}
Also, as mentioned in the comments mutable keys are not a good idea, please check
ArrayList's hashCode()
HashMap Implementation
TheHashMap
is implemented as a Hash table containing an array of buckets. Each key's hashCode()
is mapped to a bucket array index and different keys are allowed to have the same hashcode ([hashCode contract][4]). That's why each bucket can contain multiple key/value pair arranged via a Linked data structure. So, when you're invoking the get
method, first a bucket is selected by mapping the key's hashCode to the bucket index, then the target entry is searched by calling the equals()
method on the key.
Code Explanation
As shown from the output, after adding the second pair with a different list as the key, we can see that the map's size is still 1. This is because you've used the exact same references (obj1 and obj2) to build both the first and second key, yielding the same hashCode for list1 and list2 as the ArrayList's hashCode is built upon its elements. Once, the second pair is added to the HashMap, its key's hashCode returns the same value of the first key, indexing the same bucket and then replacing the first pair once the second keyequals
the key of the first pair.
Now getting to your question. The situation described would have taken place even if the List's elements had been different references with same values as long the NewClass
' equals()
method had been defined on the exact three fields passed to the constructor (int, String, String). My guess is that the NewClass' equals()
method hasn't been defined or it confronts different fields. Both equals()
and hashCode
should work on the same set of fields. In fact, if we define your NewClass
as follows, also the third addition will replace the only pair contained within the HashMap.
public class Test {
public static void main(String[] args) {
List<NewClass> list1 = new ArrayList<>();
List<NewClass> list2 = new ArrayList<>();
NewClass obj1 = new NewClass(1, "ddd", "[email protected]");
NewClass obj2 = new NewClass(2, "ccc", "[email protected]");
list1.add(obj1);
list1.add(obj2);
list2.add(obj1);
list2.add(obj2);
Map<List<NewClass>, Integer> mapClass = new HashMap<>();
mapClass.put(list1, 1234);
mapClass.put(list2, 4567);
System.out.println(mapClass.size());
System.out.println(mapClass.get(list1));
NewClass obj4 = new NewClass(1, "ddd", "[email protected]");
NewClass obj5 = new NewClass(2, "ccc", "[email protected]");
List<NewClass> list3 = new ArrayList<>();
list3.add(obj4);
list3.add(obj5);
System.out.println(mapClass.get(list3));
System.out.println(list1.hashCode());
System.out.println(list2.hashCode());
System.out.println(list3.hashCode());
}
}
class NewClass {
int id;
String s1, s2;
public NewClass(int id, String s1, String s2) {
this.id = id;
this.s1 = s1;
this.s2 = s2;
}
public int hashCode() {
return Objects.hash(id, s1, s2);
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != getClass()) return false;
NewClass nc = (NewClass) obj;
return nc.id == id && Objects.equals(s1, nc.s1) && Objects.equals(s2, nc.s2);
}
}