登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java Gatherer.Downstream 怎样在结束阶段发送尾部元素

来源:17golang原创

时间:2026-11-14 05:42:07 336浏览 收藏

Java Gatherer.Downstream 发送尾部元素的关键,是把“输入结束”交给 finisher 处理,而不是在 integrator 里猜测下一次输入是否存在。每次输入先进入状态;状态凑满一组就立即 push,还没凑满的最后一组留到 finisher,再通过同一个下游发送。这样既不会丢掉尾部数据,也不会把完整分组重复发送。

官方文档:https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/util/stream/Gatherer.Downstream.html

要点速览
  • integrator 负责接收输入和发送已经完成的结果。
  • finisher 只处理上游结束时残留的状态,空状态不输出。
  • push 返回 false 表示下游不再接受后续元素;默认 combiner 则意味着按顺序执行。

先分清 integrator 与 finisher 的职责

Gatherer 是 Stream 的中间操作,既可以逐个输入地产生输出,也可以在上游结束时做一次最终动作。一个实用的分组场景是:每 3 个数字输出一个完整列表,末尾不足 3 个的数字也要保留。

阶段负责什么不要做什么
integrator接收当前元素,更新状态,满组后 push等待一个不存在的“下一个元素”
finisher检查残留状态并发送尾部组把空状态发送成空分组
Downstream接受或拒绝当前输出被长期保存到状态对象中

官方 API 对 Downstream.push 的约定是:返回 true 表示还可以继续发送,返回 false 后后续 push 都不会再被接受。因此,尾部发送前可以用 isRejecting() 做一次尽力判断;但它不是替代 push 返回值的绝对保证。

Java Gatherer integrator 与 finisher 共享状态并通过 Downstream 输出完整分组和尾部元素的结构说明图
图1:Java Gatherer 的 integrator、状态、finisher 与 Downstream 关系说明图,不是运行截图。

用 ofSequential 保存未完成分组

下面的示例只做顺序分组,因此使用 Gatherer.ofSequential。状态是当前尚未发送的列表;达到批大小后先复制,再发送,再清空原状态。发送副本很重要,否则后续 clear() 会让下游看到被修改的列表。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Gatherer;

static Gatherer, List> batchesWithTail(int batchSize) {
    if (batchSize  {
            state.add(element); // 先把当前输入放入未完成分组
            if (state.size()  {
            if (!state.isEmpty() && !downstream.isRejecting()) {
                downstream.push(List.copyOf(state)); // 上游结束,发送最后的不完整分组
            }
        }
    );
}

例如输入 1, 2, 3, 4, 5 且 batchSize=3 时,结果是 [[1, 2, 3], [4, 5]]。输入为空时,状态从未加入元素,finisher 不会制造一个空列表。

Java Gatherer 分组状态在输入结束时由 finisher 将剩余元素发送给 Downstream 的流程结构说明图
图2:不满一组的状态在 finisher 中发送为尾部结果的说明图,不是运行截图。

短路、空输入与并行边界

不要无条件忽略 push 的返回值。下游可能由 findFirst 等短路操作构成,完整分组发送时返回 false,上游就不应继续生产。finisher 的返回类型是 void,它只能在发送前检查状态和下游状态;真正的接受结果仍由 push 表达。

这个实现选择 ofSequential,因为“当前尾部状态”有明确的顺序含义。若改用可并行的 Gatherer.of,就必须提供能合并两个分区状态的 combiner,并重新定义跨分区的分组顺序。仅仅把 ofSequential 换成 of,会让尾部结果的边界变得不可靠。

常见问题

finisher 会在每个输入元素后执行吗?

不会。它对应上游结束阶段;每个元素的处理仍由 integrator 完成。

为什么要使用 List.copyOf?

因为发送后要清空可变状态。复制后,下游拿到的是不会被后续 clear() 改写的结果。

finisher 里能继续保存 Downstream 吗?

不能。Downstream 只应在当前回调调用期间使用,状态对象也不应长期持有它。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>