Using Lambda expression to realize super sorting function

Posted by phast1 on Fri, 28 Jan 2022 13:03:59 +0100

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
Comparator based sorting
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
In comparator Sort reversal is defined in comparing
The 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

Topics: PHP Front-end