Advanced usage of sorting from zero-row single-row Java 8-List in conjunction with Lambdas

Keywords: Java Lambda JDK

brief introduction

In this tutorial, we will first learn about Lambda support in Java 8, especially how to use it to write Comparator s and sort Collection s.

First, let's define a simple entity class:

public class Human {
    private String name;
    private int age;
}

Simple sorting of lists

Prior to Java 8, sorting collections would involve creating anonymous inner classes for Comparator used in sorting:

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

This is relatively simple. Let me look at the case of unit testing.

@Test
public void givenPreLambda() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    Collections.sort(humans, new Comparator<Human>() {
        @Override
        public int compare(Human h1, Human h2) {
            return h1.getName().compareTo(h2.getName());
        }
    });
    Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

The Use of Lambda in Sorting

With the introduction of Lambdas, we can now bypass anonymous inner classes and achieve the same results through simple, functional semantic implementations:

(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());

Again, you can use previous test cases:

@Test
public void test() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    humans.sort(
      (Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));
  
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

Note that we also used the new sort API added to java.util.List in Java 8, rather than the old Collections.sort API.

Sorting without Type Definition

We can further simplify expressions by not specifying type definitions - compilers can infer these by themselves:

(h1, h2) -> h1.getName().compareTo(h2.getName())

For example:

@Test
public void test() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
  
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

Thanks to Lambda's method support, this makes my code more concise.

Sorting using static methods

Next, we will use Lambda Expression to perform sorting and refer to static methods.

First, we will define the method compare ByNameThenAge with the comparison method in the Comparator < Human > object with exactly the same return value:

public static int compareByNameThenAge(Human lhs, Human rhs) {
    if (lhs.name.equals(rhs.name)) {
        return lhs.age - rhs.age;
    } else {
        return lhs.name.compareTo(rhs.name);
    }
}

Now let's see how to use it.

humans.sort(Human::compareByNameThenAge);

Look at unit testing

@Test
public void test() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    humans.sort(Human::compareByNameThenAge);
    Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

Sorting of internal API s

We can also compare sorting by using Collections references and Comparator. comparison method combinations.

We will use getName() to construct Lambda expressions and sort lists by name:

@Test
public void test() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    Collections.sort(
      humans, Comparator.comparing(Human::getName));
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

Reverse sorting

Java 8 also introduces an auxiliary method for inverting comparators, which we can quickly use to invert our sorting:

@Test
public void test() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10), 
      new Human("Jack", 12)
    );
     
    Comparator<Human> comparator
      = (h1, h2) -> h1.getName().compareTo(h2.getName());
     
    humans.sort(comparator.reversed());
  
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

Multiple Conditional Sorting

Comparing lambda expressions is not necessarily very simple. We can also write more complex expressions. For example, sort and compare by name and age.

@Test
public void test() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 12), 
      new Human("Sarah", 10), 
      new Human("Zack", 12)
    );
     
    humans.sort((lhs, rhs) -> {
        if (lhs.getName().equals(rhs.getName())) {
            return lhs.getAge() - rhs.getAge();
        } else {
            return lhs.getName().compareTo(rhs.getName());
        }
    });
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

Multi-Conditional Combination Sorting

For the same example, we can also use Comparator's new combination support.

Starting with JDK 8, we can now combine multiple comparators to build more complex comparison logic:

@Test
public void test() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 12), 
      new Human("Sarah", 10), 
      new Human("Zack", 12)
    );
 
    humans.sort(
      Comparator.comparing(Human::getName).thenComparing(Human::getAge)
    );
     
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

Stream sorting

We can also sort collections using the Stream sorted() API of Java 8.

We can sort stream s using natural sort and sort provided by comparators. To this end, we have sorted(), which corresponds to two API s:

  • Sort (); Sort Stream's elements by sorting, and the element class must implement the Comparable interface
  • Sorted (Comparator <? Super T > comparator); sort elements according to Comparator instances

Let's look at an example of how to use the sorted() method of natural sorting:

@Test
public final void test() {
    List<String> letters = Lists.newArrayList("B", "A", "C");
     
    List<String> sortedLetters = letters.stream().sorted().collect(Collectors.toList());
    assertThat(sortedLetters.get(0), equalTo("A"));
}

Now let's see how we can use custom Comparator and sorted():

@Test
public final void test() {   
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
    Comparator<Human> nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
     
    List<Human> sortedHumans = humans.stream().sorted(nameComparator).collect(Collectors.toList());
    assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}

If we use the Comparator.comparing() method, we can further simplify the above example:

@Test
public final void test() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
  
    List<Human> sortedHumans = humans.stream()
      .sorted(Comparator.comparing(Human::getName))
      .collect(Collectors.toList());
       
    assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}

Stream reverse sort

We can also use Stream.sorted() to reverse sort lists.

First, let's look at an example of how to sort lists in reverse order by combining the sorted() method with Comparator.reverseOrder():

@Test
public final void test() {
    List<String> letters = Lists.newArrayList("B", "A", "C");
 
    List<String> reverseSortedLetters = letters.stream()
      .sorted(Comparator.reverseOrder())
      .collect(Collectors.toList());
       
    assertThat(reverseSortedLetters.get(0), equalTo("C"));
}

Now let's see how to use the sorted() method and customize Comparator:


@Test
public final void test() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
    Comparator<Human> reverseNameComparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
 
    List<Human> reverseSortedHumans = humans.stream().sorted(reverseNameComparator)
      .collect(Collectors.toList());
    assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}

Finally, let's use the Comparator.comparing() method to simplify the above example:

@Test
public final void test() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
 
    List<Human> reverseSortedHumans = humans.stream()
      .sorted(Comparator.comparing(Human::getName, Comparator.reverseOrder()))
      .collect(Collectors.toList());
     
    assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}

summary

Using Java 8 Lambda expression to sort List s is very good, and it is also one of Lambda's usage scenarios, which shows the powerful semantic function of Lambda.

Posted by nutshell on Thu, 16 May 2019 21:34:42 -0700