Refactoring Comparators in Java 8

In this article, I will show how we can refactor our old ugly comparators to cool elegant one.

Example Domain Class

Employee {  
    private int salary;
    private int yearsOfExperience;
    int getSalary() { return salary; }
    public int getYearsOfExperience() { return yearsOfExperience; }
}

Use Case

Sort a list of Employee class by salaryyearsOfExperience etc.

Java 7 Code

Sorting By Salary
    employees.sort(new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o1.getSalary() - o2.getSalary();
        }
    });
Sorting By Years of Experience
    employees.sort(new Comparator<Employee>() {
        @Override
        public int compare(Employee o1, Employee o2) {
            return o1.getSalary() - o2.getSalary();
        }
    });
This code looks ugly. Actually, it became ugly after the introduction of lambda operator.

Refactoring in Java 8

Java8 introduced a new operator named lambda operator using which we can replace functional interfaces with a lambda expression. Functional interfaces are those where there is only one abstract method, like Comparator.
    employees.sort((Employee e1, Employee e2) -> {
        return e1.getSalary() - e2.getSalary();
    });

    employees.sort((Employee e1, Employee e2) -> {
        return e1.getYearsOfExperience() - e2.getYearsOfExperience();
    });
Now, this code looks a lot cleaner we have removed the unnecessary object instantiation.
Since lambdas can be returned from functions we can write separate methods for those expressions.
public static Comparator<Employee> salaryComparator() {
    return (o1, o2) -> o1.getSalary() - o2.getSalary();
}

public static Comparator<Employee> yearsOfExrienceComparator() {
    return (e1, e2) -> e1.getYearsOfExperience() - e2.getYearsOfExperience();
}

// So our sorting code now becomes
employees.sort(salaryComparator());
employees.sort(yearsOfExrienceComparator());
This code can be further improved. Since both comparator functions look very similar we can write a generic one which will look something like this
public static Comparator<Employee> intComparator() {
}
This function is incomplete. We have to pass some behavior to it to extract proper fields from Employee objects like salary and yearsOfExperience. Sounds like a perfect place for lambda.
For this, we first need a functional interface for extracting fields from Employee class.
interface IntExtractor<Employee> {
    int extractInt(Employee object);
}
We don't care about implementation now since we will provide it dynamically. We can use this in our generic comparator.
public static Comparator<Employee> intComparator(IntExtractor<Employee> extractor) {
    return (Employee e1, Employee e2) -> extractor.extractInt(e1) - extractor.extractInt(e2);
}
Now we can use this generic comparator to sort our collection.
 employees.sort(intComparator((Employee e) -> e.getSalary()));
 employees.sort(intComparator((Employee e) -> e.getYearsOfExperience()));
This looks elegant. We are passing behavior to our intComparator. This code can be further improved using method reference. So our final code can be will be this.
interface IntExtractor<Employee> {
    int extractInt(Employee object);
}

public static Comparator<Employee> intComparator(IntExtractor<Employee> extractor) {
    return (Employee e1, Employee e2) -> extractor.extractInt(e1) - extractor.extractInt(e2);
}

employees.sort(intComparator(Employee::getSalary));
employees.sort(intComparator(Employee::getYearsOfExperience));
Simple, elegant and functional. You might say that this is too much work for sorting a list. You are not wrong. In fact, you don't have to write the generic comparator and int extractor. Both of them are already defined java.util. We can just statically import them.
Full example using Java's built-in
import java.util.ArrayList;  
import java.util.Comparator;  
import java.util.List;

public class ComparatorTest {  
    public static void main(String[] args) {

        List<Employee> employees = new ArrayList<>();

        employees.sort(Comparator.comparingInt(Employee::getSalary));
        employees.sort(Comparator.comparingInt(Employee::getYearsOfExperience));
    }
}

class Employee {  
    private int salary;
    private int yearsOfExperience;

    Employee(int weight, int yearsOfExperience) {
        this.salary = weight;
        this.yearsOfExperience = yearsOfExperience;
    }

    int getSalary() {
        return salary;
    }

    public int getYearsOfExperience() {
        return yearsOfExperience;
    }
} 
That's all for today. Happy coding.

Comments

Popular posts from this blog

Handling Exception Using Spring's Aspect Oriented Programming

What is a View in oracle?