Jacky
Jacky

Reputation: 11

Why the Java TreeSet reject adding the elements with the same comparing key instead of equals key?

I'm using TreeSet to save unique employees and sort by name. But I got strange results. Here is the code:


import java.util.Comparator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;

class Employee implements Comparable<Employee> {
    private int id;
    private String name;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // Two Employees are equal if their IDs are equal
    @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;
    }

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

    // Compare employees based on their IDs
    @Override
    public int compareTo(Employee employee) {
        return this.getId() - employee.getId();
    }

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

    public static void main(String[] args) {
        SortedSet<Employee> employees = new TreeSet<>();

        // Providing a Custom Comparator (This comparator compares the employees based on their Name)
        employees = new TreeSet<>(Comparator.comparing(Employee::getName));

        employees.add(new Employee(1010, "Rajeev"));
        employees.add(new Employee(1011, "Rajeev"));
        employees.add(new Employee(1005, "Sachin"));
        employees.add(new Employee(1008, "Chris"));

        System.out.println("\nEmployees (sorted based on the supplied Comparator)");
        System.out.println(employees);
    }
}

Here is the output:

Employees (sorted based on the supplied Comparator)
[Employee{id=1008, name='Chris'}, Employee{id=1010, name='Rajeev'}, Employee{id=1005, name='Sachin'}]

I'm using openJDK-11.0.1_1_macosx. The object employees.add(new Employee(1011, "Rajeev")); was rejected. I thought it rejects the object with the duplicated employ.id but not the employ.name.

Thanks for your help.

Upvotes: 1

Views: 373

Answers (1)

dpr
dpr

Reputation: 10964

From TreeSets javadoc:

Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the {@code Set} interface. (See {@code Comparable} or {@code Comparator} for a precise definition of consistent with equals.) This is so because the {@code Set} interface is defined in terms of the {@code equals} operation, but a {@code TreeSet} instance performs all element comparisons using its {@code compareTo} (or {@code compare}) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the {@code Set} interface.

The Name-Comparator is not consistent with the equals implementation of Employee as equals only checks for the employee id. That is the two "Rajeev" employees are equal from a comparator perspective but not from an equals perspective. To make this work as desired you should include the ID as second comparator criterium:

employees = new TreeSet<>(Comparator.comparing(Employee::getName)
              .thenComparing(Employee::getId));

Upvotes: 4

Related Questions