To successfully store and retrieve objects from a hashtable, the objects used as keys must implement the hashCode method and the equals method.

Now that we know what to implement for a class that is used as a key in a map, we may came up with:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

classKey{

privateStringvalue;

publicKey(Stringvalue){

this.value=value;

}

publicvoidsetValue(Stringvalue){

this.value=value;

}

publicStringgetValue(){

returnvalue;

}

@Override

publicbooleanequals(Objectobj){

if(this==obj)returntrue;

if(obj==null||getClass()!=obj.getClass())returnfalse;

Key other=(Key)obj;

returnvalue.equals(other.value);

}

@Override

publicinthashCode(){

returnvalue.hashCode();

}

@Override

publicStringtoString(){

returnvalue;

}

}

At first glance this class looks right, it has both equals and hashCode so what could go wrong? If you look more carefully you’ll notice that it is mutable. In this case it is possible that a key is changed after it is added to the map. If this happens, prepare yourself for severe headaches. Let’s look at the following scenario:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

publicclassHowToLoseAnObjectInHashMap{

publicstaticvoidmain(String...args){

Map<Key,String>map=newHashMap<>();

Key key=newKey("key1");

map.put(key,"value1");

printMapAndValue(map,newKey("key1"));

key.setValue("key2");

printMapAndValue(map,newKey("key1"));

printMapAndValue(map,newKey("key2"));

key.setValue("key1");

printMapAndValue(map,newKey("key1"));

}

staticvoidprintMapAndValue(Map<Key,String>map,Key key){

System.out.println("-------------------------------------");

System.out.println("map: "+map);

System.out.println("map.get("+key+"): "+map.get(key));

}

}

After the key was added to the map, I changed its value. Lookup does not work for both keys:
* key1 is not found because the map contains only one key, key2.
* key2 is not found because it has a different hashCode so the lookup is done in another bucket.

Running the example displays:

1

2

3

4

5

6

7

8

9

10

11

12

13

-------------------------------------

map:{key1=value1}

map.get(key1):value1

-------------------------------------

map:{key2=value1}

map.get(key1):null

-------------------------------------

map:{key2=value1}

map.get(key2):null

-------------------------------------

map:{key1=value1}

map.get(key1):value1

If no value is returned, we might realize that we have a problem and fix it. Unfortunately things can go really wrong and we can have a situation where both keys have the same hashCode. In this case the value is returned with the wrong key. The simple way to show this is to change the hashCode implementation with:

1

2

3

4

5

@Override

publicinthashCode(){

return1;//value.hashCode();

}

and run the example again:

1

2

3

4

5

6

7

8

9

10

11

12

13

-------------------------------------

map:{key1=value1}

map.get(key1):value1

-------------------------------------

map:{key2=value1}

map.get(key1):null

-------------------------------------

map:{key2=value1}

map.get(key2):value1

-------------------------------------

map:{key1=value1}

map.get(key1):value1

In this case we found the value with the wrong key, key2.

The simplest fix for this problem is to make the class of the key immutable. If that is not possible, make sure that keys are not changed.