Thursday 26 November 2015

Why Key of a Map must be Immutable ?

Immutable Object :
An immutable object is a special kind of object whose state can not be changed once it is created. These objects are used in concurrent applications as the states can not be changed, So there is no question of corrupting the state by thread interference. And one more major use is while working with java.util.Map the key must be an immutable object. Else once you have added one entry to the map and after that if the state of the key got changed then you will lose the object mapping.

Example:
class User {
    String email;
    String name;
    public User(String email, String name) {
        this.email = email;
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        if (!email.equals(user.email)) return false;
        return name.equals(user.name);
    }
    @Override    public int hashCode() {
        int result = email.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}
public class ImmutableKeyTest {
    public static void main(String[] args) {
        Map<User, String> userDealMap = new HashMap<>();
        User user = new User("abc@gmail.com", "ABC");
        User user1 = new User("xyz@gmail.com", "XYZ");
        userDealMap.put(user, "HAPPY50");
        userDealMap.put(user1, "HAPPY25");
        System.out.println(userDealMap);

        System.out.println("Before Key Change");
        System.out.println(userDealMap.get(user));
        user.setName("ABC DEF");
        System.out.println("After Key Change");
        System.out.println(userDealMap.get(user));
    }
}
Output: {User@b42dca6f=HAPPY25, User@4a84e2cf=HAPPY50} Before Key Change HAPPY50 After Key Change null Here when the name of the user got changed, it impacts the hashCode of the user object, so while getting the value from the map, it treats the supplied key as a new key and returns NULL.

Solution: Solution is very simple ! Make the User object as immutable object. (Guideline for creating immutable object refer https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html)

final class User {
    final String email;
    final String name;
    public User(String email, String name) {
        this.email = email;
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public String getName() {
        return name;
    }
    @Override    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        if (!email.equals(user.email)) return false;
        return name.equals(user.name);
    }
    @Override    public int hashCode() {
        int result = email.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}
Once you make it immutable, you can not change the state of the object by any chance.
// user.setName("ABC DEF"); // This will give compilation error as no setter method there

No comments:

Post a Comment