Using Lambda expression to realize super sorting function

In the process of system development, sorting data is a very common scenario. Generally speaking, we can adopt two ways:

With the help of the sorting function of the storage system (supported by SQL, NoSQL and NewSQL), the query result is the ordered result
The query results are unordered data and sorted in memory.
Today I want to talk about the second sorting method, which realizes data sorting in memory.

First, we define a basic class, and then we will demonstrate how to sort in memory according to this basic class.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
Copy code

Sort based on Comparator
Before Java 8, we used to complete sorting by implementing the Comparator interface, such as:

new Comparator<Student>() {
    @Override
    public int compare(Student h1, Student h2) {
        return h1.getName().compareTo(h2.getName());
    }
};
Copy code

Here is the definition of anonymous internal class. If it is a general comparison logic, you can directly define an implementation class. It is also relatively simple to use, as follows:

@Test
void baseSortedOrigin() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    Collections.sort(students, new Comparator<Student>() {
        @Override
        public int compare(Student h1, Student h2) {
            return h1.getName().compareTo(h2.getName());
        }
    });
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

Junit5 is used to implement unit testing, which is very suitable for verifying logic.

Because the Comparator defined uses the name field to sort. In Java, the sorting of String type is determined by the ASCII code sequence of single character. J is in front of T, so Jerry is in the first place.

Replace Comparator anonymous inner class with Lambda expression
Those who have used java 8's lambba should know that anonymous inner classes can be simplified to Lambda expressions as follows:

Collections.sort(students, (Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
Copy code

In Java 8, the sort method is added to the List class, so collections Sort can be directly replaced by:

students.sort((Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
Copy code

According to the type inference of Lambda in Java 8, we can abbreviate the specified Student type:

students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
Copy code

So far, our whole sorting logic can be simplified as follows:

@Test
void baseSortedLambdaWithInferring() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

Extract public Lambda expressions through static methods
We can define a static method in Student:

public static int compareByNameThenAge(Student s1, Student s2) {
    if (s1.name.equals(s2.name)) {
        return Integer.compare(s1.age, s2.age);
    } else {
        return s1.name.compareTo(s2.name);
    }
}
Copy code

This method needs to return an int type parameter. In Java 8, we can use this method in Lambda:

@Test
void sortedUsingStaticMethod() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort(Student::compareByNameThenAge);
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

With the help of Comparator's comparing method
In Java 8, the Comparator class adds a comparing method, which can use the passed Function parameters as comparison elements, such as:

@Test
void sortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort(Comparator.comparing(Student::getName));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

Multi condition sorting
We show multi conditional sorting in the static method section, and can also implement multi conditional logic in the Comparator anonymous inner class:

@Test
void sortedMultiCondition() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12),
            new Student("Jerry", 13)
    );
    students.sort((s1, s2) -> {
        if (s1.getName().equals(s2.getName())) {
            return Integer.compare(s1.getAge(), s2.getAge());
        } else {
            return s1.getName().compareTo(s2.getName());
        }
    });
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

From a logical point of view, multi condition sorting is to judge the first level conditions first, if they are equal, then judge the second level conditions, and so on. In Java 8, comparing and a series of thenmatching can be used to represent multi-level conditional judgment. The above logic can be simplified as follows:

@Test
void sortedMultiConditionUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12),
            new Student("Jerry", 13)
    );
    students.sort(Comparator.comparing(Student::getName).thenComparing(Student::getAge));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

There can be more than one thenmatching method here, which is used to represent multi-level conditional judgment, which is also the convenience of functional programming.

Sort in Stream
Java 8 not only introduces Lambda expressions, but also introduces a new streaming API: Stream API, in which the sorted method is also used to sort elements in streaming calculation, which can be passed into Comparator to realize sorting logic:

@Test
void streamSorted() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
Copy code

Similarly, we can simplify writing through Lambda:

@Test
void streamSortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = Comparator.comparing(Student::getName);
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
Copy code

Reverse order
Transfer sort judgment
Sorting is to judge the order according to the value returned by the compareTo method. If you want to arrange in reverse order, just return the returned value:

@Test
void sortedReverseUsingComparator2() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
    students.sort(comparator);
    Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
Copy code

As you can see, when we are in positive order, we are H1 getName(). CompareTo (H2. Getname()), here we directly reverse it and use H2 getName(). CompareTo (H1. Getname()) achieves the effect of negation. In Java collections, a Java util. Collections. The internal private class of reversecomparator implements element inversion in this way.

Reverse order with the help of Comparator's reversed method

The reversed method is added in Java8 to realize reverse order, which is also very simple to use:

@Test
void sortedReverseUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
    students.sort(comparator.reversed());
    Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
Copy code
 stay Comparator.comparing Sort reversal defined in
comparing Method also has an overloaded method, java.util.Comparator#Comparing (Java. Util. Function. Function <? Super T,? Extends U >, Java. Util. Comparator <? Super U >), the second parameter can be passed into comparator Reverseorder() can be used to reverse the order:

@Test
void sortedUsingComparatorReverse() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    students.sort(Comparator.comparing(Student::getName, Comparator.reverseOrder()));
    Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Copy code

Define sort reversal in Stream
The operation in Stream is similar to direct list sorting. You can reverse the Comparator definition or use Comparator Reverseorder(). The implementation is as follows:

@Test
void streamReverseSorted() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
    final List<Student> sortedStudents = students.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}

@Test
void streamReverseSortedUsingComparator() {
    final List<Student> students = Lists.newArrayList(
            new Student("Tom", 10),
            new Student("Jerry", 12)
    );
    final List<Student> sortedStudents = students.stream()
            .sorted(Comparator.comparing(Student::getName, Comparator.reverseOrder()))
            .collect(Collectors.toList());
    Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
Copy code

Judgment of null value
In the previous examples, the sorting of valued elements can cover most scenarios, but sometimes we still encounter the case of null in elements:

The element in the list is null
The field of the element in the list participating in the sorting condition is null
If we still use the previous implementations, we will encounter NullPointException, that is, NPE. Let's briefly demonstrate:

@Test
void sortedNullGotNPE() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    Assertions.assertThrows(NullPointerException.class,
            () -> students.sort(Comparator.comparing(Student::getName)));
}
Copy code

So we need to consider these scenarios.

The element is a clumsy implementation of null
The first thought is to judge the air:

@Test
void sortedNullNoNPE() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort((s1, s2) -> {
        if (s1 == null) {
            return s2 == null ? 0 : 1;
        } else if (s2 == null) {
            return -1;
        }
        return s1.getName().compareTo(s2.getName());
    });

    Assertions.assertNotNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNull(students.get(2));
}
Copy code

We can extract a Comparator from the empty judgment logic and realize it through combination:

class NullComparator<T> implements Comparator<T> {
    private final Comparator<T> real;

    NullComparator(Comparator<? super T> real) {
        this.real = (Comparator<T>) real;
    }

    @Override
    public int compare(T a, T b) {
        if (a == null) {
            return (b == null) ? 0 : 1;
        } else if (b == null) {
            return -1;
        } else {
            return (real == null) ? 0 : real.compare(a, b);
        }
    }
}
Copy code

This implementation has been prepared for us in Java 8.

Use comparator Nullslast and comparator nullsFirst
Use comparator Nullslast implements null at the end:

@Test
void sortedNullLast() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort(Comparator.nullsLast(Comparator.comparing(Student::getName)));
    Assertions.assertNotNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNull(students.get(2));
}
Copy code

Use comparator Nullsfirst implements null at the beginning:

@Test
void sortedNullFirst() {
    final List<Student> students = Lists.newArrayList(
            null,
            new Student("Snoopy", 12),
            null
    );
    students.sort(Comparator.nullsFirst(Comparator.comparing(Student::getName)));
    Assertions.assertNull(students.get(0));
    Assertions.assertNull(students.get(1));
    Assertions.assertNotNull(students.get(2));
}
Copy code

Is it very simple? Next, let's see how to realize the logic that the field of sorting condition is null.

The field of the sort condition is null
This is the combination with the help of Comparator. It's like a doll. You need to use Comparator twice Nullslast, the implementation is listed here:

@Test
void sortedNullFieldLast() {
    final List<Student> students = Lists.newArrayList(
            new Student(null, 10),
            new Student("Snoopy", 12),
            null
    );
    final Comparator<Student> nullsLast = Comparator.nullsLast(
            Comparator.nullsLast( // 1
                    Comparator.comparing(
                            Student::getName,
                            Comparator.nullsLast( // 2
                                    Comparator.naturalOrder() // 3
                            )
                    )
            )
    );
    students.sort(nullsLast);
    Assertions.assertEquals(students.get(0), new Student("Snoopy", 12));
    Assertions.assertEquals(students.get(1), new Student(null, 10));
    Assertions.assertNull(students.get(2));
}
Copy code

The code logic is as follows:

Code 1 is the first layer of null safe logic, which is used to judge whether the element is null;
Code 2 is the second layer of null safe logic, which is used to judge whether the condition field of the element is null;
Code 3 is the conditional Comparator, which is used here Naturalorder(), because String sorting is used, can also be written as String::compareTo. If it is a complex judgment, you can define a more complex Comparator. The combination mode is so easy to use that one layer is not enough and another layer is set.
Conclusion
This article demonstrates the use of Lambda expressions in Java 8 to realize various sorting logic, and the new syntax is really sweet.

Green mountains don't change and green water flows. I'll see you next time.

last
If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts

If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu esteem it a favor!

PHP learning manual: https://doc.crmeb.com
Technical exchange forum: https://q.crmeb.com

Keywords: Java nosql crmeb

Added by Rother2005 on Sat, 29 Jan 2022 22:33:32 +0200