Comparator interface and Lambda expression

Introduction

After you have learned Lambda expressions and Java function references, you can learn more about the Comparator interface. It provides many useful methods and many functional interface parameters for you to use. The essential purpose is to allow you to It is possible to write a sorting method as simply as possible.

Note: The complete code is provided at the end of this article.

Sort method without using Lambda expression

Suppose we have some Person objects, each object has a String name, and we want to sort this Person array or collection according to the length of the name. What method will we think of?

We would naturally think of letting the Person object inherit the Comparable interface and override the compare method so that the way to compare two Person objects is based on the name length:

class Person implements Comparable<Person> {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Person o) {
        return this.name.length() - o.name.length();
    }
}

But there will be a problem, that is, if we want to have multiple sorting methods for the array or collection where Person is located in the future, this method will not be able to achieve multiple sorting methods.

Therefore, students with a solid foundation may think that we can customize the comparator and then implement different sorting methods by passing different custom comparator objects:

public class LambdaTest {
    public static void main(String[] args){
        Person[] list = new Person[3];
        list[0] = new Person("Zhu","Ya","Xuan");
        list[1] = new Person("Yi","Wen","Nian");
        list[2] = new Person("Fu","Xiao","Yu");

        // Manually create the comparator and use it
        Arrays.sort(list, new PersonLengthComparator());
    }
}

class PersonLengthComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return Integer.compare(o1.getName().length(),o2.getName().length());
    }
}

But we will find that this is really troublesome. We have to write a separate auxiliary class every time to complete a sorting strategy with different functions.

So is there a way that allows us to avoid writing so much complicated code?

Lambda is born for simplicity

A lambda is a transitive block of code that can be executed once or multiple times at a later time.

Java will automatically generate an object required by the target based on the lambda expression and pass it to other code for use.

Arrays.sort(list, (a,b) -> a.getName().length() - b.getName().length());

We can safely understand the above example like this: We provide a comparator for the sort method. When this comparator compares two objects, it confirms the compare based on the name length of the first object minus the name length of the second object. The result value of the comparison, the system confirms their size based on the result of this comparison value during execution.

How about it? Do you feel that this kind of code language is very similar to human language? This is the power of lambda.

Of course, the system’s Comparator also provides us with some useful methods that allow us to do a better job of the above formula:

Arrays.sort(list, Comparator.comparingInt(a -> a.getName().length()));

Some useful methods of the Comparator interface

The specific code has been placed in the complete code below. The following only briefly describes the features:

1. You can provide a comparator for the keys extracted by the comparing method.

2. Use .thenComparing to provide multi-level comparisons.

3. You can provide a Comparator.nullsFirst adapter, which will modify the existing comparator.

Note: When using nullsFirst, you must provide a modified comparator parameter in this method. If there are no other requirements except for null value processing, you must also provide a Comparator.naturalOrder() placeholder.

4. There is a static method Comparator.reverseOrder() that provides a reverse comparator.

5. Since we only need to provide a Comparator object, the above content also applies to arrays and collections.

6. If the sorting operation is used on an immutable collection, an UnsupportedOperationException will be thrown.

Note: This exception usually means that you are trying to modify an unmodifiable collection. (See code below for details)

import javax.swing.Timer;
import java.util.*;

public class LambdaTest {
    public static void main(String[] args){
        Person[] list = new Person[3];
        list[0] = new Person("Zhu","Ya","Xuan");
        list[1] = new Person("Yi","Wen","Nian");
        list[2] = new Person("Fu","Xiao","Yu");


        // Manually create the comparator and use it
        Arrays.sort(list, new PersonLengthComparator());
        // Use lambda expression to omit the process of custom Comparator comparator
        Arrays.sort(list, (a,b) -> a.getName().length() - b.getName().length());
        Arrays.sort(list, Comparator.comparingInt(a -> a.getName().length()));
        // You can provide a comparator for the keys extracted by the comparing method
        Arrays.sort(list, Comparator.comparing(Person::getName, (o1, o2) -> o1.length() - o2.length()));
        Arrays.sort(list, Comparator.comparing(Person::getName, Comparator.comparingInt(String::length)));
        // You can use the comparingInt method with lambda expression to optimize the above statement, same as above
        Arrays.sort(list, Comparator.comparingInt(p -> p.getName().length()));
        // Use .thenComparing to provide multi-level comparisons
        Arrays.sort(list, Comparator.comparingInt((Person p) -> p.getFirstName().length())
                .thenComparingInt(p -> p.getMiddleName().length()));

        // Comparator.nullsFirst is an adapter that modifies an existing comparator
        // (You can also think that the second parameter of the comparing method is such an adapter)
        // Might be empty if key function (here Person::getMiddleName)
        // You can provide the nullsFirst adapter so that normal comparison can be performed even when the first object to be compared is null.
        // Comparator.naturalOrder() represents normal comparison (this parameter cannot be omitted when using nullsFirst)
        Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
                Comparator.nullsFirst(Comparator.naturalOrder())));
        // Of course you can also add an ordinary comparator inside
        Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
                Comparator.nullsFirst(Comparator.comparingInt(String::length))));
        // There is also a static method Comparator.reverseOrder() that provides a reverse comparator
        Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
                Comparator.reverseOrder()));

        // Since we are just providing a Comparator object, the above applies equally to arrays and collections
        List personList = new ArrayList<>(Arrays.asList(list));
        personList.sort(Comparator.comparing(Person::getMiddleName, Comparator.reverseOrder()));
        // Pay special attention to the fact that the following operations will throw UnsupportedOperationException in the above sorting.
        // List personList = Arrays.stream(list).toList();
        // This exception usually means you are trying to modify an immutable collection
        //The reason for this exception is that toList, a method starting with to, returns an unmodifiable view.

        System.out.println(Arrays.toString(list));
        
        //Another use of lambda expressions
        var timer = new Timer(1000, e -> System.out.println("Output 1 line every 1 second"));
        timer.start();
        while(true){}
    }
}

class PersonLengthComparator implements Comparator {

    @Override
    public int compare(Person o1, Person o2) {
        return Integer.compare(o1.getName().length(),o2.getName().length());
    }
}

class Person implements Comparable {
    public Person(String firstName, String middleName, String lastName) {
        this.firstName = firstName;
        this.middleName = middleName;
        this.lastName = lastName;
        this.name = firstName + middleName + lastName;
    }

    private String firstName;
    private String middleName;
    private String lastName;
    private String name;

    @Override
    public String toString() {
        return name;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int compareTo(Person o) {
        return this.name.length() - o.name.length();
    }
}

The knowledge points of the article match the official knowledge archives, and you can further learn related knowledge. Java skill treeBehavioral abstraction and LambdaLambda expression 139101 people are learning the system