Java 8 – Powerful Comparison with Lambdas – Java 8 –与Lambdas的强大对比

最后修改: 2013年 12月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to take a first look at the Lambda support in Java 8, specifically how to leverage it to write the Comparator and sort a Collection.

在本教程中,我们将初步了解Java 8中的Lambda支持,特别是如何利用它来编写Comparator并对一个集合进行排序

This article is part of the “Java – Back to Basic” series here on Baeldung.

这篇文章是Baeldung网站上“Java–回到基础 “系列的一部分。

First, let’s define a simple entity class:

首先,让我们定义一个简单的实体类。

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

    // standard constructors, getters/setters, equals and hashcode
}

2. Basic Sort Without Lambdas

2.没有Lambdas的基本排序

Before Java 8, sorting a collection would involve creating an anonymous inner class for the Comparator used in the sort:

在Java 8之前,对一个集合进行排序需要为排序中使用的Comparator创建一个匿名内类。

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

This would simply be used to sort the List of Human entities:

这将简单地用于对ListHuman实体进行排序。

@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
    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)));
}

3. Basic Sort With Lambda Support

3.支持Lambda的基本排序

With the introduction of Lambdas, we can now bypass the anonymous inner class and achieve the same result with simple, functional semantics:

随着Lambdas的引入,我们现在可以绕过匿名内类,以简单的功能语义实现同样的结果。

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

Similarly, we can now test the behavior just as before:

同样地,我们现在可以像以前一样测试行为。

@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
    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)));
}

Notice that we’re also using the new sort API added to java.util.List in Java 8 instead of the old Collections.sort API.

注意,我们还使用了Java 8中添加到java.util.List的新的sort API,而不是旧的Collections.sort API。

4. Basic Sorting With No Type Definitions

4.没有类型定义的基本排序

We can further simplify the expression by not specifying the type definitions; the compiler is capable of inferring these on its own:

我们可以通过不指定类型定义来进一步简化表达式;编译器能够自行推断出这些

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

Again, the test remains very similar:

同样,测试仍然非常相似。

@Test
public void 
  givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {
    
    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)));
}

5. Sort Using Reference to Static Method

5.使用对静态方法的引用进行排序

Next we’re going to perform the sort using a Lambda Expression with a reference to a static method.

接下来,我们将使用一个Lambda表达式来执行排序,并引用一个静态方法。

First, we’re going to define the method compareByNameThenAge with the exact same signature as the compare method in a Comparator<Human> object:

首先,我们要定义方法compareByNameThenAge,其签名与compare方法在Comparator<Human>对象中完全相同。

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

Then we’re going to call the humans.sort method with this reference:

然后我们要用这个引用调用humans.sort方法。

humans.sort(Human::compareByNameThenAge);

The end result is a working sorting of the collection using the static method as a Comparator:

最终的结果是使用静态方法作为Comparator对集合进行工作排序。

@Test
public void 
  givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
    
    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)));
}

6. Sort Extracted Comparators

6.对提取的比较器进行排序

We can also avoid defining even the comparison logic itself by using an instance method reference and the Comparator.comparing method, which extracts and creates a Comparable based on that function.

我们还可以通过使用实例方法引用Comparator.comparing方法,提取并创建一个基于该函数的Comparable,甚至避免定义比较逻辑本身。

We’re going to use the getter getName() to build the Lambda expression and sort the list by name:

我们将使用getter getName()来构建Lambda表达式,并按名称对列表进行排序。

@Test
public void 
  givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() {
    
    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)));
}

7. Reverse Sort

7.反向排序

JDK 8 has also introduced a helper method for reversing the comparator. We can make quick use of that to reverse our sort:

JDK 8还引入了一个用于反转比较器的辅助方法。我们可以快速使用该方法来反转我们的排序。

@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    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)));
}

8. Sort With Multiple Conditions

8.用多个条件排序

The comparison lambda expressions need not be this simple. We can write more complex expressions as well, for example sorting the entities first by name, and then by age:

比较lambda表达式不需要这么简单。我们也可以编写更复杂的表达式,例如,先按名字对实体进行排序,然后再按年龄排序。

@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
    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 Integer.compare(lhs.getAge(), rhs.getAge());
        } else {
            return lhs.getName().compareTo(rhs.getName());
        }
    });
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

9. Sort With Multiple Conditions – Composition

9.多重条件下的排序–构成

The same comparison logic, first sorting by name and then by age, can also be implemented by the new composition support for Comparator.

同样的比较逻辑,先按姓名排序,再按年龄排序,也可以由Comparator的新组合支持实现。

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

从JDK 8开始,我们现在可以将多个比较器连在一起,以建立更复杂的比较逻辑。

@Test
public void 
  givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
    
    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)));
}

10. Sorting a List With Stream.sorted()

10.用Stream.sorted()对列表进行排序

We can also sort a collection using Java 8’s Stream sorted() API. 

我们还可以使用Java 8的Stream sorted() API对一个集合进行排序。

We can sort the stream using natural ordering, as well as ordering provided by a Comparator. For this, we have two overloaded variants of the sorted() API:

我们可以使用自然排序以及由Comparator提供的排序来对流进行排序。

  • sorted() sorts the elements of a Stream using natural ordering; the element class must implement the Comparable interface.
  • sorted(Comparator<? super T> comparator) – sorts the elements based on a Comparator instance

Let’s look at an example of how to use the sorted() method with natural ordering:

让我们看一个例子,看看如何使用自然排序的sorted()方法

@Test
public final void 
  givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
    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 a custom Comparator with the sorted() API:

现在让我们来看看我们如何在sorted() API下使用一个自定义的Comparator

@Test
public final void 
  givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {	
    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)));
}

We can simplify the above example even further if we use the Comparator.comparing() method:

如果我们使用Comparator.comparing()方法,我们可以进一步简化上述例子。

@Test
public final void 
  givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
    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)));
}

11. Sorting a List in Reverse With Stream.sorted()

11.用Stream.sorted()对一个列表进行反向排序

We can also use Stream.sorted() to sort a collection in reverse.

我们也可以使用Stream.sorted()来对一个集合进行反向排序。

First, let’s see an example of how to combine the sorted() method with Comparator.reverseOrder() to sort a list in the reverse natural order:

首先,让我们看一个例子,如何将sorted()方法与Comparator.reverseOrder()结合起来,对一个列表进行反向自然排序

@Test
public final void 
  givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    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 we can use the sorted() method and a custom Comparator:

现在让我们看看我们如何使用sorted()方法和一个自定义的Comparator

@Test
public final void 
  givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    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)));
}

Note that the invocation of compareTo is flipped, which is responsible for the reversing.

请注意,compareTo的调用是翻转的,这就是负责反转的原因。

Finally, let’s simplify the above example by using the Comparator.comparing() method:

最后,让我们通过使用Comparator.comparing()方法来简化上面的例子。

@Test
public final void 
  givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    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)));
}

12. Null Values

12.零值

So far, we implemented our Comparators in a way that they can’t sort collections containing null values. That is, if the collection contains at least one null element, then the sort method throws a NullPointerException:

到目前为止,我们实现了我们的Comparators的方式,它们不能对包含null值的集合进行排序。也就是说,如果集合至少包含一个null元素,那么sort方法就会抛出NullPointerException

@Test(expected = NullPointerException.class)
public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE() {
    List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12));

    humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
}

The simplest solution is to handle the null values manually in our Comparator implementation:

最简单的解决方案是在我们的比较器实现中手动处理值。

@Test
public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast() {
    List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);

    humans.sort((h1, h2) -> {
        if (h1 == null) {
            return h2 == null ? 0 : 1;
        }
        else if (h2 == null) {
            return -1;
        }
        return h1.getName().compareTo(h2.getName());
    });

    Assert.assertNotNull(humans.get(0));
    Assert.assertNull(humans.get(1));
    Assert.assertNull(humans.get(2));
}

Here we’re pushing all null elements towards the end of the collection. To do that, the comparator considers null to be greater than non-null values. When both are null, they are considered equal.

在这里,我们要把所有的null元素推向集合的末尾。要做到这一点,比较器认为null大于非null值。当两者都是null时,它们被认为是相等的。

Additionally, we can pass any Comparator that is not null-safe into the Comparator.nullsLast() method and achieve the same result:

此外,我们可以将任何不是空安全的比较器传入Comparator.nullsLast()方法,并实现同样的结果

@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() {
    List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);

    humans.sort(Comparator.nullsLast(Comparator.comparing(Human::getName)));

    Assert.assertNotNull(humans.get(0));
    Assert.assertNull(humans.get(1));
    Assert.assertNull(humans.get(2));
}

Similarly, we can use Comparator.nullsFirst() to move the null elements towards the start of the collection:

同样,我们可以使用Comparator.nullsFirst()来将null元素向集合的开始移动。

@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() {
    List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);

    humans.sort(Comparator.nullsFirst(Comparator.comparing(Human::getName)));

    Assert.assertNull(humans.get(0));
    Assert.assertNull(humans.get(1));
    Assert.assertNotNull(humans.get(2));
}

It’s highly recommended to use the nullsFirst() or nullsLast() decorators, as they’re more flexible and readable.

强烈建议使用nullsFirst()nullsLast()装饰器,因为它们更加灵活和可读。

13. Conclusion

13.结论

This article illustrated the various and exciting ways that a List can be sorted using Java 8 Lambda Expressions, moving right past syntactic sugar and into real and powerful functional semantics.

本文介绍了使用Java 8 Lambda Expressions对List进行排序的各种令人兴奋的方式,直接超越了语法糖,进入了真正强大的功能语义中。

The implementation of all of these examples and code snippets can be found over on GitHub.

所有这些示例和代码片段的实现都可以在 GitHub 上找到over on GitHub