Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are custom objects not equivalent keys for a HashMap?

I'm having trouble using my own class as a key for a HashMap

 public class ActorId {
     private final int playerId;
     private final int id;

     ActorId(int playerId, int id) {
         this.playerId = playerId;
         this.id = id;
     }

     public boolean equals(ActorId other) {
         return this.id == other.id && this.playerId == other.playerId;
     }

     public int hashCode() {
         int hash = 1;
         hash = hash * 31 + playerId;
         hash = hash * 31 + id;
         return hash;
     }

     public String toString() {
         return "#" + playerId + "." + id;
     }

     public int getPlayerId() {
         return playerId;
     }
 }

Here is a failing JUnit test

 import static org.junit.Assert.*;
 import java.util.Map;
 import org.junit.Test;

 public class ActorIdTest {
     @Test
     public final void testAsMapKey() {
         ActorId a = new ActorId(123, 345);
         ActorId b = new ActorId(123, 345);

         assertTrue(a.equals(b));
         assertEquals(a.hashCode(), b.hashCode());

         // Works with strings as keys
         Map<String, String> map1 = new java.util.HashMap<String, String>();

         map1.put(a.toString(), "test");
         assertEquals("test", map1.get(a.toString()));
         assertEquals("test", map1.get(b.toString()));
         assertEquals(1, map1.size()); 

         // But not with ActorIds
         Map<ActorId, String> map2 = new java.util.HashMap<ActorId, String>();

         map2.put(a, "test");
         assertEquals("test", map2.get(a));
         assertEquals("test", map2.get(b)); // FAILS here
         assertEquals(1, map2.size()); 

         map2.put(b, "test2");
         assertEquals(1, map2.size());
         assertEquals("test2", map2.get(a));
         assertEquals("test2", map2.get(b));
     }
 }
like image 822
dlundquist Avatar asked Jun 05 '11 09:06

dlundquist


People also ask

Can we have custom object as key in HashMap?

We can conclude that to use a custom class for a key, it is necessary that hashCode() and equals() are implemented correctly. To put it simply, we have to ensure that the hashCode() method returns: the same value for the object as long as the state doesn't change (Internal Consistency)

What are the constraints needs to follow for the custom objects as a key in HashMap?

Therefore, to use an object as a key in HashMap , HashSet , or Hashtable in Java, we need to override equals and hashcode methods of that object since default implementation of these methods simply check for the instance equality.

Why it is suggested to have immutable objects as keys in HashMap?

Immutabiility is required, in order to prevent changes on fields used to calculate hashCode() because if key object return different hashCode during insertion and retrieval than it won't be possible to get object from HashMap.

Can we use mutable objects as keys in HashMap?

If you want to make a mutable object as a key in the hashmap, then you have to make sure that the state change for the key object does not change the hashcode of the object. This can be done by overriding the hashCode() method. But, you must make sure you are honoring the contract with equals() also.


1 Answers

You need to change

public boolean equals(ActorId other) {
    ....
}

to

public boolean equals(Object other) {
    ....
}

Tip of the day: Always use @Override annotation.

If you had used the @Override annotation, the compiler would have caught the error and said:

The method equals(ActorId) of type ActorId must override or implement a supertype method

like image 150
aioobe Avatar answered Sep 21 '22 16:09

aioobe