سؤال

I've been going through implementation examples of Comparable vs Comparator interface.

But, I've been stuck at one point in it's implementation :

Suppose, I've a simple class : Employee which has a default sorting mechanism based on employee name.

public class Employee implements Comparable<Employee> {

   private int empSalary;
   private String empName;

   @Override
   public int compareTo(Employee e) {
        return this.empName.compareTo(e.empName);
   }

}

But, let's say, I've to sort based on employee name first, and then if two employess have same name, i've to sort them according to their salary.

So, I wrote a custom comparator to sort based on salary like below

public class SalaryComparator implements Comparator<Employee> {

      @Override
      public int compare(Employee e1, Employee e2)  {
        return e1.empSalary - e2.empSalary;
      }

}

But, when I ran my test class to sort based on name first, salary second, the output is not as what is expected.

Collections.sort(employeeList, new SalaryComparator());

Input Order :

Name : Kumar, Salary : 40
Name : Sanket, Salary : 10
Name : Kumar, Salary : 20

Expected output :

Name : Kumar, Salary : 20
Name : Kumar, Salary : 40
Name : Sanket, Salary : 10

Actual Output :

Name : Sanket, Salary : 10 // incorrect order
Name : Kumar, Salary : 20
Name : Kumar, Salary : 40
هل كانت مفيدة؟

المحلول

This is not because your Employee class already has a default ordering, that using Collections.sort with a custom comparator will introduce a new layer of ordering.

For example let's say that the default ordering of your Employees is by their salary in ascending order. Now let's say you want to sort them by salary in descending order.

According to your logic how this will behave?

Collections.sort(employees, new SalaryDescendingComparator());

The fact is that when you provide a custom comparator to Collections.sort, it will use only this one and not the sorting mechanism that you implemented in your Employee class.

As the doc states:

Sorts the specified list according to the order induced by the specified comparator.

So because the SalaryComparator compares employees only by their salary, that's why you get this output.

If you want to sort by name first and then by salary, you'll have to do this in one time, i.e :

public class Employee implements Comparable<Employee> {

   private int empSalary;
   private String empName;

   @Override
   public int compareTo(Employee e) {
       int cmp = this.empName.compareTo(e.empName);
       return cmp != 0 ? cmp : Integer.compare(empSalary, e.empSalary);
   }

}

نصائح أخرى

If you wanted to create a custom comparator to separate name-ties based on salary, have the comparator first try to compare the two employees, then if that's a tie, use salary:

public class SalaryComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee e1, Employee e2)  {
        if (e1.compareTo(e2) == 0)
            return e1.empSalary - e2.empSalary;
        return e1.compareTo(e2);
    }
}

Note that you can do it the following way in Java 8:

public class Employee {
    private int empSalary;
    private String empName;

    public int getEmpSalary() {
        return empSalary;
    }

    public String getEmpName() {
        return empName;
    }
}

Note that I added a public getter, then for simple comparison:

List<Employee> employeeList = new ArrayList<>();
// Populate it
employeeList.sort(Comparator.comparingInt(Employee::getEmpSalary));

This will create a Comparator<Employee> that compares the integer values returned by Employee.getEmpSalary(), I am using a method reference there, which is a fancy way of writing employee -> employee.getEmpSalary(), which is a lambda function mapping from Employee to int.

For reverse you could use:

employeeList.sort(Comparator.comparingInt(Employee::getEmpSalary).reversed());

Now if you want to compare first the name, and the salary, you can use:

employeeList.sort(
        Comparator.comparing(Employee::getEmpName)
        .thenComparingInt(Employee::getEmpSalary)

Instead of creating a new Comparator with all your conditions in it, like suggested by others, you can also sort multiple time.

So if you replace your sort part with this:

Collections.sort(employeeList, new SalaryComparator());
Collections.sort(employeeList);

Then you'll have your expected output.

It might be less efficient than sorting everything at the same time with a combined comparator, but it is quite handy when you need to sort with multiple properties.

And if you really need to have a powerful way of sort by different properties (like in a table), then you should try to create a chain of comparators.
Here is an example of such a pattern:
http://commons.apache.org/proper/commons-collections/javadocs/api-2.1.1/org/apache/commons/collections/comparators/ComparatorChain.html
(This is the old way to do what is proposed in the answer using Java8 :)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top