Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparator based on different nullable fields of an object

I have an Employee object which contains two fields name and jobTitle. For sorting the employee objects, first priority should be on jobTitle, if jobTitle is null then the sorting should be based on name.

Below is the Employee object

public class Employee {
    private String name;
    private String jobTitle;
}

I used chained Comparator with JobTitlecomparator and NameComparator to achieve this:

public class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    @SafeVarargs
    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee emp1, Employee emp2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(emp1, emp2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

public class EmployeeJobTitleComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee emp1, Employee emp2) {
        if(emp1.getJobTitle() != null && emp2.getJobTitle() != null){
            return emp1.getJobTitle().compareTo(emp2.getJobTitle());
        } else {
            return 0;
        }
    }
}

public class EmployeeNameComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee emp1, Employee emp2) {
        return emp1.getName().compareTo(emp2.getName());
    }
}

public class SortingMultipleAttributesExample {
    public static void main(String[] args) {
        List<Employee> listEmployees = new ArrayList<Employee>();
        listEmployees.add(new Employee("Tom", "Developer"));
        listEmployees.add(new Employee("Sam", null));
        listEmployees.add(new Employee("Tim", "Designer"));
        listEmployees.add(new Employee("Bob", null));
        listEmployees.add(new Employee("Peter", null));
        listEmployees.add(new Employee("Craig", "Programmer"));

        Collections.sort(listEmployees, new EmployeeChainedComparator(new EmployeeJobTitleComparator(), new EmployeeNameComparator()
                ));

        for(Employee emp : listEmployees){
            System.out.println("Employee Job: "+emp.getJobTitle()+" Employee Name: "+emp.getName());
        }
    }
}

Now I should get the output like this

Employee Job: Designer Employee Name: Tim
Employee Job: Developer Employee Name: Tom
Employee Job: Programmer Employee Name: Craig
Employee Job: null Employee Name: Bob
Employee Job: null Employee Name: Peter
Employee Job null Employee Name: Sam

But I'm not getting the desired result as I expected. I'm getting the output like this

Employee Job Developer Employee Name Tom
Employee Job null Employee Name Sam
Employee Job Designer Employee Name Tim
Employee Job null Employee Name Bob
Employee Job null Employee Name Peter
Employee Job Programmer Employee Name Craig

Can anyone help me on how to achieve this?

like image 603
Digital Avatar asked Sep 15 '16 19:09

Digital


People also ask

How does Comparator handle null values?

When both elements are null, then they are considered equal. When both elements are non-null, the specified Comparator determines the order. If specified comparator is null, then the returned comparator considers all non-null elements equal.

How do you compare objects fields by fields?

reflect. Field is used to compare two field objects. This method compares two field objects and returns true if both objects are equal otherwise false. The two Field objects are considered equal if and only if when they were declared by the same class and have the same name and type.

Which classes have Comparator () method?

This interface is present in java. util package and contains 2 methods compare(Object obj1, Object obj2) and equals(Object element). Using a comparator, we can sort the elements based on data members. For instance, it may be on roll no, name, age, or anything else.

Can we use compareTo in Comparator?

Using the Comparable interface and compareTo() method, we can sort using alphabetical order, String length, reverse alphabetical order, or numbers. The Comparator interface allows us to do the same but in a more flexible way.


2 Answers

Since you're using Java 8, you can use the built-in comparator facilities instead of creating your own comparators. Comparing the job title and then the name can easily be done with

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

How to handle null values is also built-in with the nullsLast and nullsFirst methods. Those methods wrap an existing comparator into a null safe comparator, putting null values either at the end or at the start.

As such, you can have:

import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsLast;

// ...

Comparator<Employee> comparator = 
    comparing(Employee::getJobTitle, nullsLast(naturalOrder())).thenComparing(Employee::getName);

Collections.sort(listEmployees, comparator);

The comparator is created by comparing the job titles with a null safe comparator putting null values last (see also). For equal titles, it is thenComparing the name of the employees.

like image 114
Tunaki Avatar answered Oct 04 '22 22:10

Tunaki


If either of the titles is null, then the two Employees will evaluate as equals, even if one of them is not null. That's not what you want. You want all null titles to be equal to each other, but not non-null values.

Replace your compare method with this:

public int compare(Employee emp1, Employee emp2) {
    if(emp1.getJobTitle() == null && emp2.getJobTitle() == null){
        return 0;
    }
    if(emp1.getJobTitle() == null) return 1;
    if(emp2.getJobTitle() == null) return -1;
    return emp1.getJobTitle().compareTo(emp2.getJobTitle());
}

And you should get the results you expect.

like image 24
resueman Avatar answered Oct 04 '22 21:10

resueman