文章目录

  • 一、简单认识:
  •   Stream的概念:
  •   Stream 的构成:
  •   Stream Source 的生成:
  •     ⑴从 Collection 和数组生成:
  •     ⑵从 BufferedReader 生成:
  •     ⑶从静态工厂生成:
  •     ⑷自己构建:
  •     其他方式:
  •   Stream 的操作:
  •     Stream 的操作类型
  •       1、Intermediate:
  •       2、Terminal:
  •       3、Short-circuiting:
  •     Stream 的操作记录
  •     Stream 的操作叠加
  •     Stream 叠加之后的执行
  •     Stream 执行之后的结果
  • 二、使用详解:
  •   Stream 的构造:
  •     构造 Stream 的几种常见方法:
  •       1、单独的值:
  •       2、数组:
  •       3、集合:
  •     数值流的构造:
  •     流转换为其它数据结构:
  •   Stream 的操作与常用示例:
  •     forEach 循环 与 peek
  •     filter 过滤
  •     map 映射
  •     flatMap
  •     sorted 排序
  •     distinct 去重
  •     count 总数
  •     min、max
  •     skip、limit
  •     collect
  •     concat
  •     Match
  •     reduce 归纳
  •     findFirst、findAny
  •     Stream.generate
  •     Stream.iterate
  •     groupingBy、partitioningBy


一、简单认识:

Java 8中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作(bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错,但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言 + 多核时代综合影响的产物。

  Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
  原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

  Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后就用完了,就好比流水从面前流过,一去不复返。

并行化操作,而迭代器只能命令式地、串行化操作。
  当使用串行方式去遍历时,每个 item 读完后才会读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

  Stream 的另外一大特点是,数据源本身可以是无限的。

  使用一个 Stream 通常包括三个基本步骤:
    ●①获取一个数据源
    ●②数据转换
    ●③执行操作获取想要的结果

  每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。


java stream forEach循环报错 java stream foreach原理_java

      ●Collection.stream()
      ●Collection.parallelStream()
      ●Arrays.stream(T array) or Stream.of()

      ●java.io.BufferedReader.lines()

      ●java.util.stream.IntStream.range()
      ●java.nio.file.Files.walk()

      ●java.util.Spliterator

      ●Random.ints()
      ●BitSet.stream()
      ●Pattern.splitAsStream(java.lang.CharSequence)
      ●JarFile.stream()

零个或多个

        一个 Stream 只能有一个 terminal 操作,当这个操作执行后,Stream 就被使用完了,无法再被操作。所以这必定是 Stream 的最后一个操作。Terminal 操作的执行,会真正开始 Stream 的遍历,并且会生成一个结果,或者一个 side effect(副作用)。

        在对于一个Stream进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。

        ①对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
        ②对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

        当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个short-circuiting 操作是必要非充分条件。

java stream forEach循环报错 java stream foreach原理_java_02

      Stream 中使用 Stage 的概念来描述一个完整的操作,并用某种实例化后的 PipelineHelper 来代表 Stage,将具有先后顺序的各个 Stage 连到一起,就构成了整个流水线。跟 Stream 相关类和接口的继承关系如图所示:

java stream forEach循环报错 java stream foreach原理_java_03


      其中 IntPipeline、LongPipeline、DoublePipeline 这三个类是专门为三种基本类型(不是包装类型)而定制的,跟 ReferencePipeline 是并列关系,同时也含有 Head、StatelessOp 和 StatefulOp。Head 用于表示第一个 Stage,即调用诸如 Collection.stream() 方法产生的 Stage,这个 Stage 里不包含任何操作;StatelessOp 和 StatefulOp 分别表示无状态和有状态的 Stage,对应于无状态和有状态的中间操作。

java stream forEach循环报错 java stream foreach原理_回调函数_04

      通过 Collection.stream() 方法得到 Head 也就是 stage0,紧接着调用一系列的中间操作,不断产生新的 Stream。这些 Stream 对象以双向链表的形式组织在一起,构成整个流水线,由于每个 Stage 都记录了前一个 Stage 和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。这就是 Stream 记录操作的方式。

      要想让流水线起到应有的作用,需要一种将所有操作叠加到一起的方案。由于前面的 Stage 并不知道后面 Stage 到底执行了哪种操作,以及回调函数是哪种形式。换句话说,只有当前 Stage 本身才知道该如何执行自己包含的动作。这就需要有某种协议来协调相邻 Stage 之间的调用关系。

      这种协议由 Sink 接口完成,Sink 接口包含的主要方法如下表所示:

java stream forEach循环报错 java stream foreach原理_回调函数_05


      有了上面的协议,相邻 Stage 之间调用就很方便了,每个 Stage 都会将自己的操作封装到一个 Sink 里,前一个 Stage 只需调用后一个 Stage 的 accept() 方法即可,并不需要知道其内部是如何处理的。当然对于有状态的操作,Sink 的 begin() 和 end() 方法也是必须实现的。

      Sink 的四个接口方法常常相互协作,共同完成计算任务。实际上 Stream API 内部实现的的本质,就是如何重载 Sink 的这四个主要接口方法。

      有了 Sink 对操作的包装,Stage 之间的调用问题就解决了,执行时只需要从流水线的 head 开始对数据源依次调用每个 Stage 对应的 Sink.{begin()、accept()、cancellationRequested()、end()}方法就可以了。

      一种可能的 Sink.accept() 方法流程是这样的:
        ●首先使用当前 Sink 包装的回调函数处理入参。
        ●然后将处理结果传递给流水线下游的 Sink。

      Sink 接口的其他几个方法也是按照这种“处理->转发”的模型实现。

      下面,结合一个具体例子看看 Stream 的中间操作是如何将自身的操作包装成 Sink 以及 Sink 是如何将处理结果转发给下一个 Sink 的。
        ●先看 Stream.map() 方法:

// 代码位于ReferencePipeline中,调用该方法将产生一个新的Stream
@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        // opWripSink()方法返回由回调函数包装而成Sink
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                	// 使用当前Sink包装的回调函数mapper处理u,将处理结果传递给流水线下游的Sink
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}

          代码逻辑很简单,就是将回调函数 mapper 包装到一个 Sink 当中。由于 Stream.map() 是一个无状态的中间操作,所以 map() 方法返回了一个 StatelessOp 内部类对象(一个新的Stream),调用这个新 Stream 的 opWripSink() 方法将得到一个包装了当前回调函数的 Sink。

        ●再来看一个复杂一点的例子,Stream.sorted() 方法:
          Stream.sorted() 方法将对 Stream 中的元素进行排序,显然这是一个有状态的中间操作,因为读取所有元素之前是没法得到最终顺序的。
          sorted() 一种可能封装的 Sink 代码如下:

// 代码位于SortedOps中
private static final class RefSortingSink<T> extends AbstractRefSortingSink<T> {
	// 存放用于排序的元素
    private ArrayList<T> list;

    RefSortingSink(Sink<? super T> sink, Comparator<? super T> comparator) {
        super(sink, comparator);
    }

	// 创建一个存放排序元素的列表
    @Override
    public void begin(long size) {
        if (size >= Nodes.MAX_ARRAY_SIZE)
            throw new IllegalArgumentException(Nodes.BAD_SIZE);
        list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
    }

    @Override
    public void end() {
    	// 只有元素全部接收之后才能开始排序
        list.sort(comparator);
        downstream.begin(list.size());
        // 下游Sink不包含短路操作
        if (!cancellationWasRequested) {
        	// 将处理结果传递给流水线下游的Sink
            list.forEach(downstream::accept);
        }
        // 下游Sink包含短路操作
        else {
            for (T t : list) {
            	// 每次都调用cancellationRequested()询问是否可以结束处理
                if (downstream.cancellationRequested()) break;
                // 将处理结果传递给流水线下游的Sink
                downstream.accept(t);
            }
        }
        downstream.end();
        list = null;
    }

    @Override
    public void accept(T t) {
    	// 使用当前Sink包装动作处理t(只是简单的将元素添加到中间列表当中)
        list.add(t);
    }
}

      上述代码完美的展现了 Sink 的四个主要方法是如何协同工作的:
        ●首先 beging() 方法告诉 Sink 参与排序的元素个数,方便确定中间结果容器的的大小。
        ●之后通过 accept() 方法将元素添加到中间结果当中,最终执行时调用者会不断调用该方法,直到遍历所有元素。
        ●最后 end() 方法告诉 Sink 所有元素遍历完毕,启动排序步骤,排序完成后将结果传递给下游的 Sink。
        ●如果下游的 Sink 是短路操作,将结果传递给下游时不断询问下游 cancellationRequested() 是否可以结束处理。

结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。

      结束操作之后不能再有别的操作,所以结束操作不会创建新的 Stage,直观的说就是流水线的链表不会在往后延伸了。结束操作会创建一个包装了自己操作的 Sink,这也是流水线中最后一个 Sink,这个 Sink 只需要处理数据而不需要将结果传递给下游的Sink(因为没有下游)。对于 Sink 的“处理->转发”模型,结束操作的 Sink 就是调用链的出口。

java stream forEach循环报错 java stream foreach原理_回调函数_06


      再来看下上游的 Sink 是如何找到下游 Sink 的。Stream 类库的设计者设置了一个 Sink AbstractPipeline.opWrapSink(int flags, Sink downstream) 方法来得到 Sink,该方法的作用是返回一个新的包含了当前 Stage 代表的操作以及能够将结果传递给 downstream 的 Sink 对象。

      为什么要产生一个新对象而不是返回一个 Sink 字段?这是因为使用 opWrapSink() 可以将当前操作与下游 Sink(上文中的 downstream 参数)结合成新 Sink。试想只要从流水线的最后一个 Stage 开始,不断调用上一个 Stage 的 opWrapSink() 方法直到最开始(不包括 stage0,因为 stage0 代表数据源,不包含操作),就可以得到一个代表了流水线上所有操作的 Sink,用代码表示就是这样:

// 代码位于AbstractPipeline中
@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    Objects.requireNonNull(sink);

	// 从下游向上游不断包装Sink。如果到达最初传入的sink代表结束操作。
    for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    }
    // 函数返回时就可以得到一个代表了流水线上所有操作的Sink
    return (Sink<P_IN>) sink;
}

      现在流水线上从开始到结束的所有的操作都被包装到了一个 Sink 里,执行这个 Sink 就相当于执行整个流水线,执行 Sink 的代码如下:

// 代码位于AbstractPipeline中
// 对spliterator代表的数据执行wrappedSink代表的操作
@Override
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    Objects.requireNonNull(wrappedSink);

    if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
    	// 通知开始遍历
        wrappedSink.begin(spliterator.getExactSizeIfKnown());
        // 遍历
        spliterator.forEachRemaining(wrappedSink);
        // 通知遍历结束
        wrappedSink.end();
    }
    else {
        copyIntoWithCancel(wrappedSink, spliterator);
    }
}

      上述代码首先调用 wrappedSink.begin() 方法告诉 Sink 数据即将到来,然后调用 spliterator.forEachRemaining() 方法对数据进行遍历(Spliterator 是容器的一种迭代器),最后调用 wrappedSink.end() 方法通知 Sink 数据处理结束。

      流水线上所有操作都执行后,用户所需要的结果(如果有)在哪里呢?
      首先要说明的是不是所有的 Stream 结束操作都需要返回结果,有些操作只是为了使用其副作用(Side-effects),比如使用 Stream.forEach() 方法将结果打印出来就是常见的使用副作用的场景(事实上,除了打印之外其他场景都应避免使用副作用),对于真正需要返回结果的结束操作结果存在哪里呢?
      特别说明:副作用不应该被滥用,也许在 Stream.forEach() 里进行元素收集是个不错的选择,就像下面代码中那样,但遗憾的是这样使用的正确性和效率都无法保证,因为 Stream 可能会并行执行。大多数使用副作用的地方都可以使用归约操作更安全和有效的完成。

// 错误的收集方式
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
      .forEach(s -> results.add(s));  // Unnecessary use of side-effects!
      
// 正确的收集方式
List<String>results =
     stream.filter(s -> pattern.matcher(s).matches())
             .collect(Collectors.toList());  // No side-effects!

      回到流水线执行结果的问题上来,需要返回结果的流水线结果存在哪里呢?这要分不同的情况讨论,下表给出了各种有返回结果的Stream结束操作:

java stream forEach循环报错 java stream foreach原理_java_07


        ●对于表中返回 boolean 或者 Optional 的操作,由于只返回一个值,只需要在对应的 Sink 中记录这个值,等到执行结束时返回就可以了。

        ●对于归约操作,最终结果放在用户调用时指定的容器中(容器类型通过收集器指定)。collect()、reduce()、max()、min() 都是归约操作,虽然 max() 和 min() 也是返回一个 Optional,但事实上底层是通过调用 reduce() 方法实现的。

        ●对于返回是数组的情况,毫无疑问的结果会放在数组当中。这么说当然是对的,但在最终返回数组之前,结果其实是存储在一种叫做 Node 的数据结构中的。Node 是一种多叉树结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素。这样做是为了并行执行方便。


二、使用详解:

  Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

Stream stream = Stream.of("a", "b", "c");
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
List<String> list = Arrays.asList(strArray);
stream = list.stream();

    对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
// 不输出3
IntStream.range(1, 3).forEach(System.out::println);
// 输出3
IntStream.rangeClosed(1, 3).forEach(System.out::println);
// Array
String[] strArray = stream.toArray(String[]::new);

// Collection
List<String> list = stream.collect(Collectors.toList());
List<String> list = stream.collect(Collectors.toCollection(ArrayList::new));
Set set = stream.collect(Collectors.toSet());
Stack stack = stream.collect(Collectors.toCollection(Stack::new));

// String
String str = stream.collect(Collectors.joining()).toString();

只可以使用一次,上面的代码为了简洁而重复使用了数次。

    当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下:
      ●Intermediate:map(mapToInt、flatMap等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
      ●Terminal:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
      ●Short-circuiting:anyMatch、allMatch、noneMatch、findFirst、findAny、limit。

List<String> list = Arrays.asList("A", "B", "C", "D", "E");

// 方式一:JDK1.8之前的循环方式
for (String item: list) {
    System.err.println(item);
}

// 方式二:使用Stream的forEach方法
list.stream().forEach(item -> System.err.println(item));

// 方式三:方式二的简化方式
// 由于方法引用也属于函数式接口,所以方式二中的Lambda表达式也可以使用方法引用来代替
list.stream().forEach(System.err::println);
// 进一步简化可以写为
list.forEach(System.err::println);

      forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个 Stream 进行两次 terminal 运算。

      如果有类似重复使用需求,可以使用具有相似功能的 intermediate 操作 peek。

Stream.of("one", "two", "three", "four")
    .filter(e -> e.length() > 3)
    .peek(e -> System.err.println("Filtered value: " + e))
    .map(String::toUpperCase)
    .peek(e -> System.err.println("Mapped value: " + e))
    .collect(Collectors.toList());

      forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环。

List<Person> people = Arrays.asList(
    Person.builder().name("A").age(28).build(),
    Person.builder().name("B").age(18).build(),
    Person.builder().name("C").age(17).build()
);

people.stream().filter(p -> p.getAge() > 18).forEach(System.err::println);

      filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

List<String> list = Arrays.asList("A", "B", "C", "D", "E");

// 转换小写
list.stream().map(String::toLowerCase).forEach(System.err::println);

List<Integer> nums = Arrays.asList(1, 2, 3, 4);

// 转换为平方数
nums.stream().map(n -> n * n).forEach(System.err::println);

      map 生成的是个一对一的映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。

List<Integer> a = Arrays.asList(1, 2, 3);
List<Integer> b = Arrays.asList(4, 5, 6);

List<List<Integer>> collect = Stream.of(a, b).collect(Collectors.toList());
// [[1, 2, 3], [4, 5, 6]]
System.err.println(collect);

// 将多个集合中的元素合并到一个集合
List<Integer> mergeList = Stream.of(a, b).flatMap(Collection::stream).collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6]
System.err.println(mergeList);

      flatMap 把 Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起。

List<String> list = Arrays.asList("c", "e", "a", "d", "b");

list.stream().sorted((s1, s2) -> s1.compareTo(s2)).forEach(System.out::println);
// 或者简写为
list.stream().sorted(String::compareTo).forEach(System.err::println);

      对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

Stream<String> stream = Stream.of("A", "B", "C", "D", "A", "C");

stream.distinct().forEach(System.err::println);
Stream<String> stream = Stream.of("A", "B", "C", "D", "A", "C");
long count = stream.count();
System.err.println(count);
List<String> list = Arrays.asList("1", "2", "3", "4", "5");
Optional<String> optional = list.stream().min(String::compareTo);
System.err.println(optional.get());

      min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

List<String> skipList = Arrays.asList("a", "b", "c", "d", "e");

// c d e
skipList.stream().skip(2).forEach(System.err::println);

List<String> limitList = Arrays.asList("a", "b", "c", "d", "e");

// a b c
limitList.stream().limit(3).forEach(System.err::println);

      limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。

      对一个 parallel 的 Steam 管道来说,如果其元素是有序的,那么 limit 操作的成本会比较大,因为它的返回对象必须是前 n 个也有一样次序的元素。取而代之的策略是取消元素间的次序,或者不要用 parallel Stream。

List<String> list = Arrays.asList("a", "b", "c", "d", "e");

List<String> collect = list.stream().collect(Collectors.toList());

Object[] objects = list.stream().toArray();
List<String> list1 = Arrays.asList("a", "b");
List<String> list2 = Arrays.asList("c", "d");

Stream<String> concatStream = Stream.concat(list1.stream(), list2.stream());
concatStream.forEach(System.err::println);
List<String> list = Arrays.asList("A", "B", "C", "D", "E");

// parallelStream可以并行计算,速度比stream更快
boolean result = list.parallelStream().anyMatch(item -> item.equals("C"));
System.err.println(result);

      Stream 有三个 match 方法:
        ●allMatch:Stream 中全部元素符合传入的 predicate,返回 true。
        ●anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true。
        ●noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true。
      它们都不是要遍历全部元素才返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

// 字符串连接
Optional<String> optional = Stream.of("A", "B", "C", "D", "E").reduce((before, after) -> before + "," + after);
optional.ifPresent(System.err::println);

List<BigDecimal> list = Arrays.asList(
    new BigDecimal("11.11"),
    new BigDecimal("22.22"),
    new BigDecimal("33.33")
);

// 求和,有起始值
BigDecimal sum1 = list.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
System.err.println(sum1);
// 求和,无起始值
BigDecimal sum2 = list.stream().reduce(BigDecimal::add).get();
System.err.println(sum2);

// 求最小值
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
System.err.println(minValue);

// 过滤后字符串连接
String reduce = Stream.of("a", "B", "c", "D", "e", "F")
    .filter(x -> x.compareTo("Z") > 0)
    .reduce("", String::concat);
System.err.println(reduce);

      这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

System.err.println(Stream.of("A", "B", "C", "D", "E").findFirst().get());

System.err.println(Stream.of("A", "B", "C", "D", "E").findAny().get());

      这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。(它的返回值类型为 Optional)

// 生成 10 个随机整数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.err::println);

// 另一种方法
IntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.err::println);

      通过实现 Supplier 接口,可以自己控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。

      Stream.generate() 还允许用户自己实现 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。

private class PersonSupplier implements Supplier<Person> {
    private long index = 0;
    private final Random random = new Random();
    @Override
    public Person get() {
        return Person.builder().num(index++).name("A" + index).age(random.nextInt(100)).build();
    }
}

// 自实现 Supplier
Stream.generate(new PersonSupplier()).limit(10).forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
// 生成等差数列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.err.print(x + " "));

      iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。与 Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。

// 按照年龄分组
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).limit(100).collect(Collectors.groupingBy(Person::getAge));
for (Map.Entry<Integer, List<Person>> entry : personGroups.entrySet()) {
    System.err.println("Age " + entry.getKey() + " = " + entry.getValue().size());
}

// 按照未成年人和成年人分组
Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).limit(100).collect(Collectors.partitioningBy(p -> p.getAge() < 18));
System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());

      partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。