Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data lost from TreeSet when using Comparator

I have the following code which will sort the Employees's based on their experience.

I am adding 2 employees with different name and same experience. I am expecting that at the end set will have 2 employees, but I am getting only one.

I have also overridden equals and hashcode, Can any one tell me why I am getting only one employee in the set.

Test Class

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.junit.Test;

public class SetWithComparator {


    @Test
    public void testComparatorWithSet() {

        Comparator<Employee> comparator =
                (emp1, emp2) -> emp1.getYearOFExp().compareTo(emp2.getYearOFExp());

        Set<Employee> empSet = new TreeSet<>(comparator);

        Employee e1 = new Employee();
        e1.setName("Employee-1");
        e1.setYearOFExp(12f);

        Employee e2 = new Employee();
        e2.setName("Employee-2");
        e2.setYearOFExp(12f);

        empSet.add(e1);
        empSet.add(e2);

    }

}

Model Class

class Employee {


    private String name;
    private Float yearOFExp;

    public String getName() {
        return name;
    }

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

    public Float getYearOFExp() {
        return yearOFExp;
    }

    public void setYearOFExp(Float yearOFExp) {
        this.yearOFExp = yearOFExp;
    }

    @Override
    public boolean equals(Object obj) {

        if (obj instanceof Employee) {

            Employee e = (Employee) obj;
            return new EqualsBuilder().append(name, e.getName()).isEquals();
        } else {
            return false;
        }

    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(name).toHashCode();
    }

}
like image 307
Jobin Avatar asked Jan 13 '17 06:01

Jobin


Video Answer


2 Answers

Because the comparator is not consistent with your equals method. Please check the documentation of Comparator.

The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

The exact behavior that you experience is hinted in the docs of Comparable (although you use comparator):

For example, if one adds two keys a and b such that (!a.equals(b) && a.compareTo(b) == 0) to a sorted set that does not use an explicit comparator, the second add operation returns false (and the size of the sorted set does not increase) because a and b are equivalent from the sorted set's perspective.

In your case: comparator.compare(e1, e2) is 0, e1.equals(e2) is false.

like image 111
Lachezar Balev Avatar answered Sep 24 '22 20:09

Lachezar Balev


For a SortedSet, the Comparator determines what elements are the same and it won’t contain duplicates. If you don’t want to consider all employees with the same experience to be the same, you have to add a secondary ordering:

Comparator<Employee> comparator = Comparator.comparing(Employee::getYearOFExp)
                                            .thenComparing(Employee::getName);

Note that you have to include all properties that make up the identity of an employee. In your example, there is only the name, however, in real life scenarios you would have more. On the other hand, if you have an ID, that determines the identity, you don’t need to check other properties and, in fact, shouldn’t, as most properties, including the name, can change. This also applies to the implementation of equals and hashCode.

In order to warn about false assumptions about what can be assumed to be granted, gender changes are a real life fact and even birthdays may turn out to be false and need a correction.

like image 30
Holger Avatar answered Sep 24 '22 20:09

Holger