Home > Back-end >  Add unique elements in HashSet based on attributes of framework provided non-editable object classes
Add unique elements in HashSet based on attributes of framework provided non-editable object classes

Time:12-21

I am trying to add unique elements in HashSet based on Object properties comparison, in this case objects of Employee class, but ending up adding same object. In this case the Employee class is provided by a framework and it would not be possible to provide custom implementations to equals() and hashcode(). Employee.java

public class Employee {
    
    private long employeeId;
    private String name;

    //getters
    //setters

    @Override
    public String toString() {
        return "Employee{"  
                "employeeId="   employeeId  
                ", name='"   name   '\''  
                '}';
    }
}
Map<String, Set<Employee>> ackErrorMap = new HashMap<>();

Employee emp = new Employee(1,"bon");
Employee emp2 = new Employee(1,"bon");

ackErrorMap.computeIfAbsent("key1",
         x -> new HashSet<>()).add(emp);

ackErrorMap.computeIfAbsent("key1",
         x -> new HashSet<>()).add(emp2);

This would result in output as below although object attributes are same, the objects emp and emp2 are different.

"key1" : {"emp","emp2"}

Is there any way to compare objects with name of employee (in this case "bon" for emp and emp2, before adding to the HashSet,so as to avoid duplicate entries. Or would it better to use ArrayList?

Possible example using Arraylist

ackErrorMap.computeIfAbsent("key1",
         x -> new ArrayList<>()).add(emp);

CodePudding user response:

You can override the equals and hashCode methods in the Employee class. The equals method should return true if two objects are considered equal, and the hashCode method should return the same value for two objects that are considered equal.

class Employee {
    private int id;
    private String name;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return id == employee.id &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

With these changes, when you add emp and emp2 to the HashSet, only one of them will be added, because they will be considered equal based on the equals method.

CodePudding user response:

You need to override equals() and hashCode() inside Employee class. Or you can use lombok’s @EqualsAndHashCode annotation in your Employee class.

CodePudding user response:

You can create a custom type wrapping the class coming from the framework and implement the equals/hashCode contract according to your requirements.

That's how such wrapper might look like (for the purpose of conciseness I'm using a Java 16 record, but it can be implemented as a class as well).

public record EmployeeWrapper(Employee employee) {
    @Override
    public boolean equals(Object o) {
        return o instanceof EmployeeWrapper other
            && employee.getName().equals(other.employee.getName());
    }

    @Override
    public int hashCode() {
        return Objects.hash(employee.getName());
    }
}

And you can use with a Map of type Map<String,Set<EmployeeWrapper>> to ensure uniqueness of the Employee based on the name property.

I would also advise to make one step further and encapsulate the Map into a class which would expose the methods covering all scenarios of interaction with the Map (like add new entry, get employees by key, etc.), so that your client would not dial wrappers, but only with employees and wrapping and unwrapping would happen within the enclosing class.

Here's how it might be implemented:

public class AcrErrors {
    private Map<String, Set<EmployeeWrapper>> ackErrorMap = new HashMap<>();
    
    public void addEmployee(String key, Employee employee) {
        EmployeeWrapper wrapper = new EmployeeWrapper(employee);
        
        ackErrorMap
            .computeIfAbsent(key, x -> new HashSet<>())
            .add(wrapper);
    }

    public List<Employee> getEmployees(String key) {
        
        return ackErrorMap.getOrDefault(key, Collections.emptySet()).stream()
            .map(EmployeeWrapper::employee)
            .toList();
    }
    
    // other methods
}
  • Related