1. 概述

在这篇简短的文章中,我们将关注Java 9中即将引入的Stream API的新改进。

2. Stream 的 takeWhile/dropWhile

关于这些方法的讨论在StackOverflow上反复出现(最流行的一个问题是这个)。

想象一下,我们想要通过向前一个流的值添加一个字符,生成一个字符串流,直到当前流中元素的长度小于10。

在Java 8中如何解决?我们可以使用像limitallMatch这样的短路中间操作,它们实际上用于其他目的,或者根据 Spliterator 自己编写一个 takeWhile 实现(见这里),但这会复杂化一个简单的问题。

有了Java 9,解决方案变得简单:

Stream<String> stream = Stream.iterate("", s -> s + "s")
  .takeWhile(s -> s.length() < 10);

takeWhile操作接受一个谓词,它应用于流中的元素来确定匹配该谓词的元素最长前缀(如果流有序)或流的一部分元素(当流无序时)。

为了继续,我们最好理解“最长前缀”和“流的一部分”的含义:

  • 最长前缀是流中连续的一系列元素,满足给定的谓词。序列的第一个元素是流的第一个元素,序列中最后一个元素后面的元素不匹配给定的谓词。
  • 流的一部分是流中满足给定谓词的一些(但不是所有)元素的集合。

介绍这些关键术语后,我们可以轻松理解另一个新的dropWhile操作。

它与takeWhile正好相反。如果流有序,dropWhile将返回一个流,其中包含在删除匹配给定谓词的最长前缀后的剩余元素。

否则,如果流无序,dropWhile将返回一个流,其中包含在删除匹配给定谓词的部分元素后的剩余元素。

让我们使用前面获得的流丢弃前五个元素:

stream.dropWhile(s -> !s.contains("sssss"));

简单来说,dropWhile操作会在给定元素的谓语返回true时移除元素,并在遇到第一个谓语的false时停止移除。

3. Stream 的 iterate

下一个新特性是对有限流生成的重载迭代方法。不要与无限顺序流生成函数的finite变体混淆。

新的iterate方法稍微修改了此方法,增加了应用于元素的谓词,以确定流何时终止。它的用法非常方便且简洁:

Stream.iterate(0, i -> i < 10, i -> i + 1)
  .forEach(System.out::println);

它可以关联到相应的for循环:

for (int i = 0; i < 10; ++i) {
    System.out.println(i);
}

4. Stream 的 ofNullable

有时我们需要将一个元素放入流中,但这个元素可能为null。我们不希望流中包含这样的值,这导致我们不得不写if语句或三元运算符来检查元素是否为null。

假设collectionmap变量已成功创建并填充,看看以下示例:

collection.stream()
  .flatMap(s -> {
      Integer temp = map.get(s);
      return temp != null ? Stream.of(temp) : Stream.empty();
  })
  .collect(Collectors.toList());

为了避免这种样板代码,Stream类新增了一个ofNullable方法。使用这种方法,上述示例可以简化为:

collection.stream()
  .flatMap(s -> Stream.ofNullable(map.get(s)))
  .collect(Collectors.toList());

5. 总结

我们考虑了Java 9中Stream API的重大变化以及这些改进如何帮助我们用更少的努力编写出更有表现力的程序。

如往常一样,代码片段可以在GitHub上找到。