当前位置:首页 > 文章列表 > 文章 > java教程 > Java Collectors.teeing 汇总两条统计管道

Java Collectors.teeing 汇总两条统计管道

来源:17golang原创 2026-10-10 18:24:50 0浏览 收藏

我第一次把订单列表同时汇总成“订单数”和“平均金额”时,直觉写法是连续调用两次 stream()。代码能工作,但业务结果分散在两个表达式里,后面再增加最大金额或状态计数时,很快就会变成一串相互独立的遍历。Java 的 Collectors.teeing 适合处理这个问题:同一批元素同时交给两个下游 Collector,最后由一个 merger 把两个结果组合成一个对象。

官方地址:https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/stream/Collectors.html

这篇只解决一个边界明确的任务:在一次 collect 中组合两条统计管道。示例使用 Java 12 及以上提供的 Collectors.teeing,不把它包装成所有 Stream 问题的通用答案。

这个场景为什么适合 teeing

如果一批订单已经在一个 Stream 中,想同时得到数量与平均金额,目标结果可以先抽象成:

OrderSummary(totalCount, averageAmount)

传统写法通常是先收集数量、再收集金额统计;而 teeing 把“两个独立的归约结果”和“最终业务对象”放在同一次收集中表达。Oracle API 对它的定义就是一个由两个下游 Collector 组成的复合 Collector:每个输入元素都会被两个下游分别处理,两个结果完成后再交给合并函数。

这里有一个容易忽略的取舍:teeing 是组合收集器,不是让两个统计天然共享所有中间状态。它适合两个结果都能由各自 Collector 清晰表达的场景;如果两条管道有复杂的共享状态或需要严格控制遍历顺序,显式循环反而更容易维护。

先把两条统计管道拆开

先不急着写完整业务对象,分别看两个下游 Collector。第一条数订单,第二条从订单金额生成摘要统计。这样做的好处是可以先确认每条管道的结果类型,再处理合并函数的泛型。

同一批订单元素经 teeing 分流到 counting 和 summarizingDouble 两条统计管道的结构图
图1:Collectors.teeing 将同一批元素分流到两条统计管道的静态说明图。
record Order(String id, double amount) {}

List orders = List.of(
    new Order("A-100", 18.5),
    new Order("A-101", 42.0),
    new Order("A-102", 25.5)
);

var count = orders.stream()
    // 第一条管道只关心元素数量
    .collect(Collectors.counting());

var amountStats = orders.stream()
    // 第二条管道把金额映射为 double 后汇总
    .collect(Collectors.summarizingDouble(Order::amount));

System.out.println(count); // 输出 3
System.out.println(amountStats.getAverage()); // 输出 28.666666666666668

两次单独收集很直观,却也说明了重复遍历的来源。进入 teeing 后,counting() 的结果类型是 Long,summarizingDouble() 的结果类型是 DoubleSummaryStatistics;合并器必须接住这两个具体结果。

用 merger 组装结果对象

接下来把两个下游 Collector 放进同一个 collect。merger 的两个参数分别对应第一个和第二个下游结果,返回值就是整个 collect 的结果。用 record 承载最终结果,可以避免返回一个难以理解的 Map 或数组。

总订单数和平均金额经过 merger 合并为 OrderSummary 结果对象的结构图
图2:merger 把两个统计结果组装成 OrderSummary 的静态结构图。
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

record Order(String id, double amount) {}
record OrderSummary(long totalCount, double averageAmount) {}

List orders = List.of(
    new Order("A-100", 18.5),
    new Order("A-101", 42.0),
    new Order("A-102", 25.5)
);

OrderSummary summary = orders.stream()
    .collect(Collectors.teeing(
        // 下游一:计算订单数量
        Collectors.counting(),
        // 下游二:计算金额摘要
        Collectors.summarizingDouble(Order::amount),
        // 合并两个下游结果,形成领域对象
        (count, stats) -> new OrderSummary(count, stats.getAverage())
    ));

System.out.println(summary.totalCount());
System.out.println(summary.averageAmount());

这个例子最值得记住的不是语法,而是类型关系:teeing(first, second, merger) 的 merger 接收 R1 与 R2,输出 R。因此把复杂逻辑塞进两个下游 Collector,把对象组装留在最后一步,代码的职责边界会比较稳定。

把一次 collect 放进完整工作流

实际项目里,我会按“确定结果对象—选择两个下游—处理空数据—决定是否并行”的顺序落地,而不是一看到两个统计字段就直接嵌套 teeing。

  1. 先确定结果对象。 明确字段的业务含义和精度,例如平均金额是否保留原始 double,还是在合并器中转成 BigDecimal。
  2. 再选择下游 Collector。 数量可以用 counting(),数值摘要可以用 summarizingDouble(),分组后的统计也可以继续嵌套一个下游 Collector。
  3. 把空数据写进合并器。 DoubleSummaryStatistics#getAverage() 在没有元素时返回 0.0;如果业务上需要“没有平均值”而不是“平均值为 0”,应把结果字段设计成 OptionalDouble 或显式状态。
  4. 最后才考虑并行。 两条 Collector 都必须正确支持组合器,合并器本身也应是无副作用的。不要仅因为写了 parallelStream() 就假设结果更快;数据量、拆分成本和下游 Collector 的特性都要先确认。
record SafeSummary(long totalCount, Double averageAmount) {}

SafeSummary summary = orders.stream()
    .collect(Collectors.teeing(
        // 计数结果保留 long,避免把数量塞进 double
        Collectors.counting(),
        // 摘要对象负责识别空输入
        Collectors.summarizingDouble(Order::amount),
        (count, stats) -> new SafeSummary(
            count,
            // 空集合返回 null,和“平均金额正好为 0”区分开
            stats.getCount() == 0 ? null : stats.getAverage()
        )
    ));

如果两条统计管道都需要同一个复杂转换,先用 map 把输入整理成中间记录,再交给 teeing,通常比在两个下游里重复转换更清楚。

常见误区与速查

把 teeing 当成“自动提速”工具

它首先改善的是结果组合和代码表达,不应直接承诺性能提升。下游 Collector 仍然需要各自维护状态,复杂统计还可能增加内存开销。要比较性能,应使用相同数据、相同数据源和独立基准,而不是根据一次运行的感觉下结论。

忽略下游结果的真实类型

counting() 返回的是 Long,摘要 Collector 返回的是对应的统计对象;合并器参数名写得再短,也不能改变类型。遇到编译器推断困难时,先把合并器拆成具名方法,或为结果 record 明确字段类型。

为了三个以上结果无限嵌套

两个结果组合得很自然,超过两层后可读性会快速下降。此时可以先定义一个中间统计对象,或写一个自定义 Collector;如果逻辑还伴随校验、短路和错误收集,显式循环可能更合适。

需求建议写法注意点
同时得到两个独立汇总Collectors.teeingmerger 只负责组合最终结果
单个数值统计summing 或 summarizing不必为了统一形式套 teeing
复杂共享状态自定义 Collector 或显式循环优先保证可读性和可测试性
空输入有业务语义合并器显式转换状态区分“没有值”和“值为零”

回到最初的问题:Java Collectors.teeing 适合把同一批元素交给两条清晰、相互独立的统计管道,再将结果组装为一个业务对象。先设计结果类型,再选下游 Collector,最后写无副作用的 merger,通常就是最稳妥的工作流。

相关问题

  • teeing 能不能嵌套? 可以,但超过两层后建议用中间结果对象提升可读性。
  • 空集合的平均值是什么? DoubleSummaryStatistics 的平均值为 0.0;业务上需要缺失语义时应自行转换。
  • teeing 适合并行流吗? 可以参与并行收集,但要确认两个下游 Collector 及合并逻辑都能安全组合,并用基准测试验证收益。
版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
go fix modernizers 批量迁移旧标准库写法go fix modernizers 批量迁移旧标准库写法
上一篇
go fix modernizers 批量迁移旧标准库写法
Python dataclass __post_init__ 计算派生字段
下一篇
Python dataclass __post_init__ 计算派生字段
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    408次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    483次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    493次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    438次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    266次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码