“This is the ninth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Hello, I’m looking at the mountains.

This article is “Java advanced” column collected in the public number “see the mountain hut”, reply to “Java” available source code.

Sorting data is a common scenario in system development. Generally speaking, we can take two approaches:

  1. With the sorting function of the storage system (SQL, NoSQL, and NewSQL are all supported), the query result is the sorted result
  2. The query result is unordered data, sorted in memory.

Today we’re going to talk about the second sort, which is sort data in memory.

First, we define a base class from which we will later demonstrate sorting in memory.

@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(a) {
        returnObjects.hash(name, age); }}Copy the code

Based on theComparatorThe sorting

Before Java8, we implemented the Comparator interface for sorting, for example:

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

The definition of an anonymous inner class is shown here. If it is general contrast logic, you can define an implementation class directly. It is also relatively simple to use, as follows is the application:

@Test
void baseSortedOrigin(a) {
    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) {
            returnh1.getName().compareTo(h2.getName()); }}); Assertions.assertEquals(students.get(0), new Student("Jerry".12));
}
Copy the code

Junit5 is used here to implement the unit tests to verify that the logic fits well.

Because the defined Comparator uses the name field for sorting, in Java, String sorting is determined by single-character ASCII order, with J before T, so Jerry is first.

Lambda expression substitution is usedComparatorAnonymous inner class

Lamdba users of Java8 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 the code

In Java8, the sort method was added to the List class, so collections.sort can be replaced directly with:

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

Following the type inference for Lambda in Java8, we can abbreviate the specified Student type:

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

So far, the whole sorting logic can be simplified as:

@Test
void baseSortedLambdaWithInferring(a) {
    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 the code

Extract common Lambda expressions by 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 {
        returns1.name.compareTo(s2.name); }}Copy the code

This method needs to return a parameter of type int. In Java8, we can use this method in Lambda:

@Test
void sortedUsingStaticMethod(a) {
    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 the code

With the help ofComparatorthecomparingmethods

In Java8, the Comparator class added a new comparing method that uses the passed Function argument as a comparison element, such as:

@Test
void sortedUsingComparator(a) {
    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 the code

Multiconditional sort

We showed multi-conditional sorting in the static methods section, and you can also implement multi-conditional logic in the Comparator anonymous inner class:

@Test
void sortedMultiCondition(a) {
    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 {
            returns1.getName().compareTo(s2.getName()); }}); Assertions.assertEquals(students.get(0), new Student("Jerry".12));
}
Copy the code

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

@Test
void sortedMultiConditionUsingComparator(a) {
    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 the code

The thenComparing method here can be multiple, which is used to represent multi-level conditional judgment, which is also the convenience of functional programming.

inStreamSort by

In Java8, not only Lambda expressions were introduced, but also a new streaming API, the Stream API, which also has a sorted method for sorting elements in streaming computations. We can pass in a Comparator to implement sorting logic:

@Test
void streamSorted(a) {
    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 the code

Similarly, we can use Lambda to simplify writing:

@Test
void streamSortedUsingComparator(a) {
    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 the code

Arranged in reverse chronological order

Reverse the sorting judgment

Sort by the values returned by the compareTo method. If you want to sort in reverse order, simply return the values returned:

@Test
void sortedReverseUsingComparator2(a) {
    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 the code

H1.getname ().compareto (h2.getName())); h2.getName().compareto (h1.getName()) In the Java Collections defines a Java. Util. Collections. ReverseComparator internal private class, implementation elements reversal in this way.

Reversed order using the Comparator’s reversed method

The reversed method was added to Java8 and is also simple to use:

@Test
void sortedReverseUsingComparator(a) {
    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 the code

inComparator.comparingDefines sort inversion in

Comparing method and an overloaded methods, java.util.Com parator# comparing (Java. Util. The function. The function
, java.util.Comparator
), and the second parameter is passed to comparator.reverseorder (), which implements reverseOrder:

@Test
void sortedUsingComparatorReverse(a) {
    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 the code

inStreamDefines sort inversion in

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(a) {
    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(a) {
    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 the code

Null value determination

In the previous examples, valued elements are sorted, which covers most scenarios, but sometimes we still encounter null elements:

  1. The element in the list is NULL
  2. The field in which the element in the list participates in the sorting condition is NULL

If we still use the previous implementation, we get NullPointException, or NPE, to briefly demonstrate:

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

So, we need to consider these scenarios.

The element is a clumsy implementation of NULL

The first thing that comes to mind is a short call:

@Test
void sortedNullNoNPE(a) {
    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 the code

We can extract the null logic into a Comparator, which can be achieved by combining:

class NullComparator<Timplements 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 the code

This implementation was prepared for us in Java8.

useComparator.nullsLastandComparator.nullsFirst

NullsLast implements null at the end:

@Test
void sortedNullLast(a) {
    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 the code

NullsFirst implements null at the beginning:

@Test
void sortedNullFirst(a) {
    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 the code

Now let’s see how we can implement null logic for fields in a sort condition.

The field for the sort condition is NULL

NullsLast is implemented using two comparators:

@Test
void sortedNullFieldLast(a) {
    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 the code

The code logic is as follows:

  1. Code 1 is the first layer of null-safe logic that determines whether the element is null.
  2. Code 2 is the second layer of null-safe logic that determines whether the element’s conditional field is NULL.
  3. Code 3 is the conditionComparatorThis is used hereComparator.naturalOrder()Because of the use ofStringSorting, you could also write it as thetaString::compareTo. If it’s a complex judgment, you can define a more complex oneComparatorThe combination model is so good, one layer is not enough to set another layer.

At the end of the article to summarize

This article demonstrates the use of Lambda expressions in Java8 to implement a variety of sorting logic, the new syntax is really sweet.

Green hills never change, green waters always flow. See you next time.

Recommended reading

  • This article describes 24 operations for Java8 Stream Collectors
  • Java8 Optional 6 kinds of operations
  • Use Lambda expressions to achieve super sorting functions
  • Java8 Time Library (1) : Describes the time class and common apis in Java8
  • Java8 time library (2) : Convert Date to LocalDate or LocalDateTime
  • Java8 Time Library (3) : Start using Java8 time classes
  • Java8 time library (4) : check if the date string is valid
  • New features in Java8
  • New features in Java9

Hello, I’m looking at the mountains. Swim in the code, play to enjoy life. If this article is helpful to you, please like, bookmark, follow.