目录
本笔记参考自: 《On Java 中文版》
||| 终结操作:这些操作会接受一个流,并生成一个最终结果。
终结操作不会把任何东西发给某个后端的流。因此,终结操作总是我们在一个管线内可以做的最后一件事。
可以通过toArray()操作将流转换为数组:
在流操作生成的内容必须以数组形式进行使用时,toArray()就很有用了:
【例子:将随机数存储在数组中,以流的形式复用】
- import java.util.Arrays;
- import java.util.Random;
- import java.util.stream.IntStream;
-
- public class RandInts {
- private static int[] rints =
- new Random(47)
- .ints(0, 1000)
- .limit(100)
- .toArray();
-
- public static IntStream rands() {
- return Arrays.stream(rints);
- }
- }
通过这种方式,我们可以保证每次得到的是相同的流。
有两个很常见的终结操作:
通过使用parallel()操作,可以使forEach以任何顺序操作元素。
parallel()的介绍:这一操作会让Java尝试在多个处理器上执行操作。parallel()可以将流进行分割,并在不同处理器上运行每个流。
下面的例子通过引入parallel()来展示forEachOrdered(Consumer)的作用。
【例子:forEachOrdered(Consumer)的作用】
- import static streams.RandInts.*;
-
- public class ForEach {
- static final int SZ = 14;
-
- public static void main(String[] args) {
- rands().limit(SZ)
- .forEach(n -> System.out.format("%d ", n));
- System.out.println();
-
- rands().limit(SZ)
- .parallel()
- .forEach(n -> System.out.format("%d ", n));
- System.out.println();
-
- rands().limit(SZ)
- .parallel()
- .forEachOrdered(n -> System.out.format("%d ", n));
- System.out.println();
- }
- }
程序执行的结果是:

在第一个流中,因为没有使用parallel(),所以结果显示的顺序就是它们从rands()中出现的顺序。而在第二个流中,parallel()的使用使得输出的顺序发生了变化。这就是因为有多个处理器在处理这个问题(若多执行几次,会发现输出的顺序会不一样,这就是多处理器处理带来的不确定性)。
在最后一个流中,尽管使用了parallel(),但forEachOrdered()使得结果回到了原始的顺序。
所以,对非parallel()的流,使用forEachOrdered()不会有任何影响。
collect()的第二个版本,会在每次被调用时,使用Supplier生成一个新的结果集合。这些集合就是通过第二个BiConsumer组合成一个最终结果的。
可以将流元素收集到任何特定种类的集合中。例如:假设我们需要把条目最终放入到一个TreeSet中。尽管Collectors中没有特定的toTreeSet()方法,但我们可以使用Collectors.toCollection(),将任何类型的构造器引用传递给toCollection()。
【例子:将提取的单词放入到TreeSet()中】
- import java.nio.file.Files;
- import java.nio.file.Paths;
- import java.util.Arrays;
- import java.util.Set;
- import java.util.TreeSet;
- import java.util.stream.Collectors;
-
- public class TreeSetOfWords {
- public static void main(String[] arg) throws Exception {
- Set
words2 = - Files.lines(Paths.get("TreeSetOfWords.java"))
- .flatMap(s -> Arrays.stream(s.split("\\W+")))
- .filter(s -> !s.matches("\\d+")) // 删除数字
- .map(String::trim) // 删除可能存在的留白
- .filter(s -> s.length() > 2)
- .limit(100)
- .collect(Collectors.toCollection(TreeSet::new));
-
- System.out.println(words2);
- }
- }
程序执行的结果是:

在这里,Arrays.stream(s.split("\\W+"))会将接收的文本行进行分割,获得的数组会变为Stream,然后其结果又通过flatMap()被展开成一个由单词组成的Stream。
【例子:从流生成一个Map】
- import java.util.Iterator;
- import java.util.Map;
- import java.util.Random;
- import java.util.stream.Collectors;
- import java.util.stream.Stream;
-
- class Pair { // 一个基本的数据对象Pair
- public final Character c;
- public final Integer i;
-
- public Pair(Character c, Integer i) {
- this.c = c;
- this.i = i;
- }
-
- public Character getC() {
- return c;
- }
-
- public Integer getI() {
- return i;
- }
-
- @Override
- public String toString() {
- return "Pair{" +
- "c=" + c +
- ", i=" + i +
- '}';
- }
- }
-
- class RandomPair {
- Random rand = new Random(47);
-
- // 这是一个无限大的迭代器,指向随机生成的大写字母
- Iterator
capChars = rand.ints(65, 91) // 65~91,即大写字母对应的ASCII值 - .mapToObj(i -> (char) i)
- .iterator();
-
- public Stream
stream() { // 生成一个Pair流 - return rand.ints(100, 1000).distinct() // 移除流中的重复元素
- .mapToObj(i -> new Pair(capChars.next(), i)); // 组合成Pair对象的流
- }
- }
-
- public class MapCollector {
- public static void main(String[] args) {
- Map
map = - new RandomPair().stream()
- .limit(8)
- .collect(
- Collectors.toMap(Pair::getI, Pair::getC));
- System.out.println(map);
- }
- }
程序执行的结果是:
![]()
在大多数情况下,java.util.stream.Collectors中都会存在我们所需的Collector。而如果找不到我们所需要的,这时候就可以使用collect()的第二种形式。
【例子:collect()的第二种形式】
- import java.util.ArrayList;
-
- public class SpecialCollector {
- public static void main(String[] arg) throws Exception {
- ArrayList
words = - FileToWords.stream("Cheese.dat")
- .collect(ArrayList::new, // 创建新的结果集合
- ArrayList::add, // 将下一个元素加入集合中
- ArrayList::addAll); // 组合所有的ArrayList
-
- words.stream()
- .filter(s -> s.equals("cheese"))
- .forEach(System.out::println);
- }
- }
程序执行的结果是:
![]()
这些操作之所以使用reduce命名,是因为它们会获取一系列的输入元素,并将它们组合(简化)成一个单一的汇总结果。
【例子:reduce()操作的演示】
- import java.util.Random;
- import java.util.stream.Stream;
-
- class Frobnitz {
- int size;
-
- Frobnitz(int sz) {
- size = sz;
- }
-
- @Override
- public String toString() {
- return "Frobnitz{" +
- "size=" + size +
- '}';
- }
-
- // 生成器
- static Random rand = new Random(47);
- static final int BOUND = 100;
-
- static Frobnitz supply() {
- return new Frobnitz(rand.nextInt(BOUND));
- }
- }
-
- public class Reduce {
- public static void main(String[] args) {
- Stream.generate(Frobnitz::supply)
- .limit(10)
- .peek(System.out::println)
- .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1) // 若fr0的size小于50,接受fr0,否则接受fr1
- .ifPresent(System.out::println);
- }
- }
程序执行的结果是:

Forbnitz有一个自己的生成器,叫做supply()。在上述例子中,我们把方法引用Frobnitz::supply传递给了Stream.generate,因为它和Supplier
Supplier是一个函数式接口。在这里Supplier
相当于一个返回Frobnitz对象的无参函数。
在使用reduce()时,我们没有使用identity作为初始值。这意味着reduce()会生成一个Optional,当结果不为empty时,才会执行Consumer
(fr0, fr1) -> fr0.size < 50 ? fr0 : fr1
其中的第一个参数fr0是上次调用这个reduce()时带回的结果,第二个参数fr1是来自流中的新值。
【例子:Match引发的短路行为】
- import java.util.function.BiPredicate;
- import java.util.function.Predicate;
- import java.util.stream.IntStream;
- import java.util.stream.Stream;
-
- interface Matcher extends // 能够匹配所有的Stream::*Match函数的模式
- BiPredicate
, Predicate> { - }
-
- public class Matching {
- static void show(Matcher match, int val) {
- System.out.println(
- match.test(
- IntStream.rangeClosed(1, 9) // 返回一个从1~9递增的Integer序列
- .boxed()
- .peek(n -> System.out.format("%d ", n)),
- n -> n < val
- )
- );
- }
-
- public static void main(String[] args) {
- show(Stream::allMatch, 10);
- show(Stream::allMatch, 4);
-
- show(Stream::anyMatch, 2);
- show(Stream::anyMatch, 0);
-
- show(Stream::noneMatch, 5);
- show(Stream::noneMatch, 0);
- }
- }
程序执行的结果是:

BiPredicate是一个二元谓词,所以他会接受两个参数,并返回true和false。其中,第一个参数是我们要测试的数值的流,第二个参数是谓词Predicate本身。
【例子:find*操作的使用】
- import static streams.RandInts.*;
-
- public class SelectElement {
- public static void main(String[] args) {
- System.out.println(rands().findFirst().getAsInt());
- System.out.println(rands()
- .parallel().findFirst().getAsInt());
-
- System.out.println(rands().findAny().getAsInt());
- System.out.println(rands()
- .parallel().findAny().getAsInt());
- }
- }
程序执行的结果是:

无论流是否并行,findFirst()总会选择流中的第一个元素。但findAny()有些不同,在非并行的流中,findAny()会选择第一个元素,而当这个流是并行流时,findAny()有可能选择第一个元素之外的元素。
若需要某个流中的最后一个元素,可以使用reduce():
【例子:选择流中的最后一个元素】
- import java.util.Optional;
- import java.util.OptionalInt;
- import java.util.stream.IntStream;
- import java.util.stream.Stream;
-
- public class LastElement {
- public static void main(String[] args) {
- OptionalInt last = IntStream.range(10, 20)
- .reduce((n1, n2) -> n2);
- System.out.println(last.orElse(-1));
-
- // 对于非数值对象
- Optional
lastobj = - Stream.of("one", "two", "three")
- .reduce((n1, n2) -> n2);
- System.out.println(
- lastobj.orElse("不存在任何元素"));
- }
- }
程序执行的结果是:
![]()
reduce((n1, n2) -> n2)语句可以用两个元素这的后一个替换这两个元素,通过这种方式就可以获得流中的最后一个元素了。
【例子:使用String预设的Comparator获取信息】
- public class Informational {
- public static void main(String[] args) throws Exception {
- System.out.println(
- FileToWords.stream("Cheese.dat").count());
-
- System.out.println(
- FileToWords.stream("Cheese.dat")
- .min(String.CASE_INSENSITIVE_ORDER)
- .orElse("NONE"));
-
- System.out.println(
- FileToWords.stream("Cheese.dat")
- .max(String.CASE_INSENSITIVE_ORDER)
- .orElse("NONE"));
- }
- }
程序执行的结果是:

其中,max()和min()都会返回Optional,这里出现的orElse()是用来获取其中的值的。
获得数值化流相关的信息
【例子:数值化流的信息获取(以IntStream为例)】
- import static streams.RandInts.*;
-
- public class NumericStreamInfo {
- public static void main(String[] args) {
- System.out.println(rands().average().getAsDouble());
- System.out.println(rands().max().getAsInt());
- System.out.println(rands().min().getAsInt());
- System.out.println(rands().sum());
- System.out.println(rands().summaryStatistics());
- }
- }
程序执行的结果是:
