DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • How to Implement Linked Lists in Go
  • Recursive Feature Elimination in Practice
  • Text Clustering With Deepseek Reasoning

Trending

  • Navigating Change Management: A Guide for Engineers
  • Dropwizard vs. Micronaut: Unpacking the Best Framework for Microservices
  • Advancing Robot Vision and Control
  • Simplifying Multi-LLM Integration With KubeMQ
  1. DZone
  2. Data Engineering
  3. Data
  4. Using Custom Classes as Keys in HashMaps

Using Custom Classes as Keys in HashMaps

Take a look at a couple of gotchas (and their solutions) when it comes to using custom classes as keys with your HashMaps.

By 
Rohan Raju user avatar
Rohan Raju
·
Apr. 24, 18 · Tutorial
Likes (17)
Comment
Save
Tweet
Share
159.2K Views

Join the DZone community and get the full member experience.

Join For Free

Today, we are going to discuss what we need to keep in mind when we want to use our custom class as a key in HashMap.

Here, we are considering an Employee class as a key having Id, Name, DateOfBirth, Salary as the properties.

Problem Statement 1

Let's consider the implementation below of the Employee class:

public class Employee {       
    private long id;   
    private String name;   
    private Date dateOfBirth;    
    private BigDecimal salary;   
    //Getter and Setters
    // toString
}


Let's consider using the above class as a HashMap key. For the value element for the HashMap, we are choosing a string for this example:

public static void main(String[] args) {    
    HashMap<Employee,String> employeeMap = new HashMap<Employee,String>();  

    Employee employee1 = new Employee();    
    employee1.setId(1);    
    employee1.setName("Sachin");    
    employee1.setDateOfBirth(new Date(1987,2,1));    
    employee1.setSalary(new BigDecimal(100000));    

    employeeMap.put(employee1,"India");    

    // Some Business logic    
    // In the second Operation I am updating the same employee with the newly initailized Employee Object

    Employee employee2 = new Employee();    
    employee2.setId(1);    
    employee2.setName("Sachin");    
    employee2.setDateOfBirth(new Date(1987,2,1));    
    employee2.setSalary(new BigDecimal(100000));  

    // Here we wanted to update the same Employee to Japan
    employeeMap.put(employee2,"Japan");    

    System.out.println(employeeMap); 
    // Output of this will be 2 as below

    /* 
        {Employee{id=1, name='Sachin', dateOfBirth=Tue Mar 01 00:00:00 IST 3887, salary=100000}=Japan, 
        Employee{id=1, name='Sachin', dateOfBirth=Tue Mar 01 00:00:00 IST 3887, salary=100000}=India}
    */

}


Solution for Problem Statement 1

The problem was that the hashcode and the Equals method qwew generated from the Object class. employee1 and employee2's generated hashcode will be different, and two Employee objects will be present in the HashMap.

By making the Employee class implement the Object class's equals and hashcode methods, both the employee1 and employee2 hashcode and equals methods will return the same thing.

The HashMap will use the hashcode and equals method to identify the bucket where the object is present and equals to check that the properties values are same. It's going to retrieve the correct value.

Equals and hashcode implementation in the Employee class:

public class Employee {    
    private long id;    
    private String name;    
    private Date dateOfBirth;    
    private BigDecimal salary;    

    //Getter and Setters    
    // to String    
    @Override    
    public boolean equals(Object o) {        
        if (this == o) return true;        
        if (o == null || getClass() != o.getClass()) return false;        
        Employee employee = (Employee) o;        
        if (id != employee.id) return false;        
        if (name != null ? !name.equals(employee.name) : employee.name != null) return false;        
        if (dateOfBirth != null ? !dateOfBirth.equals(employee.dateOfBirth) : employee.dateOfBirth != null) return false;        
        return salary != null ? salary.equals(employee.salary) : employee.salary == null;    
    }    

    @Override    
    public int hashCode() {        
        int result = (int) (id ^ (id >>> 32));        
        result = 31 * result + (name != null ? name.hashCode() : 0);        
        result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0);
        result = 31 * result + (salary != null ? salary.hashCode() : 0);
        return result;    
    }    
}


Problem Statement 2

Employee is a mutable object. That will create problems with a HashMap. Let's look at the code below:

public static void main(String[] args) {    
    HashMap<Employee,String> employeeMap = new HashMap<Employee,String>();  
    Employee employee1 = new Employee();   
    employee1.setId(1);   
    employee1.setName("Sachin");   
    employee1.setDateOfBirth(new Date(1987,2,1));   
    employee1.setSalary(new BigDecimal(100000));
    // Step 1
    employeeMap.put(employee1,"India");   

    for (Map.Entry<Employee, String> employeeStringEntry : employeeMap.entrySet()) {
        System.out.println(employeeStringEntry.getKey().hashCode());   
    }
    // Step 2
    // Mutating the Employee Object
    employee1.setName("Rahul");    

    for (Map.Entry<Employee, String> employeeStringEntry : employeeMap.entrySet()) {
        System.out.println(employeeStringEntry.getKey().hashCode());    
    }
    // The HashMap key is mutated and in the wrong bucket for that hashcode. 
    // Step 3
    System.out.println(employeeMap.get(employee1));    
    // Returns null    

    Employee employee2 = new Employee();   
    employee2.setId(1);   
    employee2.setName("Sachin");   
    employee2.setDateOfBirth(new Date(1987,2,1));   
    employee2.setSalary(new BigDecimal(100000));

    System.out.println(employeeMap.get(employee2)); 
    // Returns null
}


Once the Employee Object is mutated, the hashcode of that object is going to change. Now, if we try to retrieve it in step 3 (with the different hashcode), it will go to a different bucket and not be able to get the value. Now, the object placed in the HashMap is lost forever.

Solution for Problem Statement 2

Make the Key object immutable so that mutation of the key will not affect the key element of the HashMap, so the HashMap will be consistent. Below is the implementation of an immutable Employee class via with Builder pattern.


public final class Employee {

    private final long id;
    private final String name;
    private final Date dateOfBirth;
    private final BigDecimal salary;

    public Employee(EmployeeBuilder employeeBuilder) {
        this.id = employeeBuilder.id;
        this.name = employeeBuilder.name;
        this.dateOfBirth = employeeBuilder.dateOfBirth;
        this.salary = employeeBuilder.salary;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Employee employee = (Employee) o;

        if (id != employee.id) return false;
        if (name != null ? !name.equals(employee.name) : employee.name != null) return false;
        if (dateOfBirth != null ? !dateOfBirth.equals(employee.dateOfBirth) : employee.dateOfBirth != null)
            return false;
        return salary != null ? salary.equals(employee.salary) : employee.salary == null;
    }

    @Override
    public int hashCode() {
        int result = (int) (id ^ (id >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (dateOfBirth != null ? dateOfBirth.hashCode() : 0);
        result = 31 * result + (salary != null ? salary.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", dateOfBirth=" + dateOfBirth +
                ", salary=" + salary +
                '}';
    }


    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Date getDateOfBirth() {
        return (Date) dateOfBirth.clone();
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public static final class EmployeeBuilder {
        private long id;
        private String name;
        private Date dateOfBirth;
        private BigDecimal salary;

        private EmployeeBuilder() {
        }

        public static EmployeeBuilder anEmployee() {
            return new EmployeeBuilder();
        }

        public static EmployeeBuilder anEmployee(Employee employee) {
            return anEmployee().withId(employee.getId()).withName(employee.getName()).withDateOfBirth(employee.getDateOfBirth()).withSalary(employee.getSalary());
        }

        public EmployeeBuilder withId(long id) {
            this.id = id;
            return this;
        }

        public EmployeeBuilder withName(String name) {
            this.name = name;
            return this;
        }

        public EmployeeBuilder withDateOfBirth(Date dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
            return this;
        }

        public EmployeeBuilder withSalary(BigDecimal salary) {
            this.salary = salary;
            return this;
        }

        public Employee build() {
            return new Employee(this);
        }
    }
}


Now update the code of the test to see if the HashMap works consistently:

public static void main(String[] args) {

    HashMap<Employee,String> employeeMap = new HashMap<Employee,String>();

    Employee employee1 = Employee.EmployeeBuilder.anEmployee().withId(1)
                                .withName("Sachin")
                                .withDateOfBirth(new Date(1987, 2, 1))
                                .withSalary(new BigDecimal(100000))
                                .build();


    employeeMap.put(employee1,"India");

    for (Map.Entry<Employee, String> employeeStringEntry : employeeMap.entrySet()) {
        System.out.println(employeeStringEntry.getKey().hashCode());
    }

    Employee immutableUpdatedEmployee1 = Employee.EmployeeBuilder.anEmployee(employee1).withName("Rahul").build();

    for (Map.Entry<Employee, String> employeeStringEntry : employeeMap.entrySet()) {
        System.out.println(employeeStringEntry.getKey().hashCode());
    }

    System.out.println(employeeMap.get(immutableUpdatedEmployee1));
    // Returns null


    Employee employee2 = Employee.EmployeeBuilder.anEmployee().withId(1)
            .withName("Sachin")
            .withDateOfBirth(new Date(1987, 2, 1))
            .withSalary(new BigDecimal(100000))
            .build();

    System.out.println(employee2.hashCode());

    System.out.println(employeeMap.get(employee2));
    // Now this works fine and it shall return  the correct object from the HashMap

}



Data structure

Opinions expressed by DZone contributors are their own.

Related

  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • How to Implement Linked Lists in Go
  • Recursive Feature Elimination in Practice
  • Text Clustering With Deepseek Reasoning

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!