在 Java 8 之前,如果要对一组 double 数值做统计,比如算出总和、平均值、最大值、最小值,通常要写一个循环,在里面累加求和、不断比较更新最大最小值,代码又长又容易出错。Java 8 的流 API 引入了一个专门解决这类问题的工具类——DoubleSummaryStatistics,它能让我们在一次遍历中同时收集所有统计数据,配合 collectors 使用非常简洁。这篇文章就来详细聊聊它的具体用法。

DoubleSummaryStatistics 是什么
DoubleSummaryStatistics 是 JDK 8 中 java.util 包下的一个类,从名字就能看出它的定位:double 数值的汇总统计。它内部维护了四个状态:计数 count、总和 sum、最小值 min、最大值 max,平均值则是通过 sum 除以 count 计算得来的。
这个类实现了 DoubleConsumer 接口,也就是说它本身就是一个消费者,可以直接调用它的 accept(double) 方法逐个接收数值并更新统计状态。同时它还提供了 combine(DoubleSummaryStatistics) 方法,用于把两个统计对象合并起来,这正是它能够支持并行流统计的关键——每个线程统计自己那部分数据,最后再合并结果。
它的常见获取方式有两种:一种是使用 Collectors.summarizingDouble() 收集器,另一种是先把流转成 DoubleStream 再调用 summaryStatistics() 方法。两种方式本质一样,选择哪种更多是写法习惯的问题。
基础用法示例
先看第一种方式,使用 Collectors.summarizingDouble。假设有一个订单列表,需要统计所有订单金额的各项指标:
import java.util.Arrays;
import java.util.List;
import java.util.DoubleSummaryStatistics;
import java.util.stream.Collectors;
public class Demo {
public static void main(String[] args) {
List<Order> orders = Arrays.asList(
new Order("A001", 129.5),
new Order("A002", 88.0),
new Order("A003", 259.9),
new Order("A004", 45.0)
);
DoubleSummaryStatistics stats = orders.stream()
.collect(Collectors.summarizingDouble(Order::getAmount));
System.out.println("订单数量: " + stats.getCount());
System.out.println("总金额: " + stats.getSum());
System.out.println("平均金额: " + stats.getAverage());
System.out.println("最小金额: " + stats.getMin());
System.out.println("最大金额: " + stats.getMax());
}
}第二种方式更直接,把流转成 DoubleStream 后调用 summaryStatistics():
DoubleSummaryStatistics stats = orders.stream()
.mapToDouble(Order::getAmount)
.summaryStatistics();
long count = stats.getCount();
double avg = stats.getAverage();两种写法输出的结果完全一致。区别在于 mapToDouble 的方式少了一层收集器的包装,可读性更好一些;而 summarizingDouble 的方式在分组统计等复杂场景下更方便,后面会提到。
主要方法一览
DoubleSummaryStatistics 提供的方法不多,但都很实用,下面逐个说明:
accept(double value):接收一个数值并纳入统计,常用于非流场景的手动统计。combine(DoubleSummaryStatistics other):合并另一个统计对象的状态,并行流内部会自动调用。getCount():返回已统计的元素个数,类型为 long。getSum():返回所有数值的总和,类型为 double。getMin():返回最小值,如果没统计过任何数据则返回Double.POSITIVE_INFINITY。getMax():返回最大值,没数据时返回Double.NEGATIVE_INFINITY。getAverage():返回算术平均值,没数据时返回Double.NaN。
需要特别注意空数据时的返回值。很多人会以为空流统计出的最小值是 0,实际上没有元素参与统计时,min 是正无穷,max 是负无穷,average 是 NaN。如果不做判断就直接拿去展示或参与计算,很容易出问题:
DoubleSummaryStatistics empty = new DoubleSummaryStatistics(); System.out.println(empty.getMin()); // Infinity System.out.println(empty.getMax()); // -Infinity System.out.println(empty.getAverage()); // NaN System.out.println(empty.getSum()); // 0.0
所以在实际使用时,建议先通过 getCount() 判断是否有数据,再做后续处理。
配合 groupingBy 做分组统计
DoubleSummaryStatistics 真正方便的地方在于和 Collectors.groupingBy 组合使用。比如要按部门统计工资情况,传统写法需要先分组再对每组分别遍历,而用 downstream 收集器一行就能搞定:
Map<String, DoubleSummaryStatistics> statsByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.summarizingDouble(Employee::getSalary)
));
statsByDept.forEach((dept, st) -> {
System.out.printf("部门: %s, 人数: %d, 平均工资: %.2f%n",
dept, st.getCount(), st.getAverage());
});这种写法只遍历一次集合,就把分组和每组的多项统计全部完成了,性能和可读性都不错。如果只需要平均值,用 Collectors.averagingDouble 就够了;但如果同时需要多份数据,与其重复遍历多次流,不如直接用 summarizingDouble 一次拿全。
使用注意事项
第一,精度问题。DoubleSummaryStatistics 内部使用 double 累加,对于精度敏感的场景(比如金融金额计算),累积误差不可避免。如果业务对精度要求高,可以考虑先转换成 BigDecimal 处理,或者对统计结果的展示位数做好控制。
第二,并行流的合并。这个类是非线程安全的,单独多个线程共享同一个实例调用 accept 会有问题。但在并行流中使用 collect 是安全的,因为流框架会为每个线程创建独立实例,最后通过 combine 合并,这正是它实现 DoubleConsumer 和提供 combine 方法的设计意图。
第三,同类工具的选择。JDK 中还有 IntSummaryStatistics 和 LongSummaryStatistics,分别对应 int 和 long 类型的统计,用法完全一样。如果数据本身是整数类型,用对应的版本可以避免不必要的装箱和精度转换。另外 DoubleStream 上还有 average()、max() 等单独方法,适合只需要一项数据的简单场景。
总结一下,DoubleSummaryStatistics 适合任何需要一次性获取多项数值统计的场景,核心记住两点:空数据时 min、max、average 的返回值比较特殊,需要先判断 count;和 groupingBy 组合可以实现高效的分组统计。掌握这些,日常处理数据统计的代码会简洁很多。
DoubleSummaryStatisticsJava Streamcollect修改时间:2026-09-07 18:04:35