在日常业务开发中,我们经常需要对数据进行统计分析。Java 8引入的DoubleSummaryStatistics类就是一个专门用于处理double类型数据统计的工具类。它能够帮助我们快速计算数据集的基本统计信息,包括计数、求和、最小值、最大值和平均值。
先看一个典型的使用场景:假设我们需要统计公司员工的薪资情况,包括员工数量、平均工资、最高工资、最低工资和工资总和。传统方式需要编写大量代码,而使用DoubleSummaryStatistics可以极大地简化这一过程。
创建一个DoubleSummaryStatistics对象非常简单,可以通过流式操作直接生成:
java复制DoubleSummaryStatistics summary = list.stream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
这行代码做了三件事:
summaryStatistics()方法生成统计对象生成统计对象后,我们可以轻松获取各种统计信息:
java复制System.out.println("员工数量:" + summary.getCount());
System.out.println("平均工资:" + summary.getAverage());
System.out.println("最高工资:" + summary.getMax());
System.out.println("最低工资:" + summary.getMin());
System.out.println("总工资:" + summary.getSum());
注意:
getAverage()方法在没有元素时会返回0,而不是抛出异常。这与数学上"0个元素的平均值无定义"不同,是API设计上的一个实用选择。
如果不使用DoubleSummaryStatistics,我们需要手动计算每个统计量:
java复制long employeeNum = list.stream().count();
OptionalDouble sumSalary = list.stream().mapToDouble(Employee::getSalary).reduce(Double::sum);
double avgSalary = sumSalary.getAsDouble() / employeeNum;
double maxSalary = list.stream().mapToDouble(Employee::getSalary).max().getAsDouble();
long maxSalaryEmployeeNum = list.stream().filter(e->e.getSalary()==maxSalary).count();
double minSalary = list.stream().mapToDouble(Employee::getSalary).min().getAsDouble();
这种实现方式有几个明显缺点:
相比之下,DoubleSummaryStatistics提供了以下优势:
实际测试表明,对于大型数据集,使用
DoubleSummaryStatistics的性能优势更加明显,因为它避免了多次遍历的开销。
DoubleSummaryStatistics内部维护了四个关键状态变量:
count:记录元素数量sum:记录元素总和min:记录最小值max:记录最大值这些变量会在每次调用accept()方法时更新。summaryStatistics()方法实际上就是创建并配置了一个DoubleSummaryStatistics对象。
DoubleSummaryStatistics的一个强大功能是能够合并多个统计对象:
java复制DoubleSummaryStatistics stats1 = list1.stream().mapToDouble(Employee::getSalary).summaryStatistics();
DoubleSummaryStatistics stats2 = list2.stream().mapToDouble(Employee::getSalary).summaryStatistics();
stats1.combine(stats2);
// 现在stats1包含了两个列表的合并统计结果
这个特性在分布式计算或分批处理数据时特别有用。
虽然DoubleSummaryStatistics对空集合有默认处理方式,但在实际应用中我们可能需要更精确的控制:
java复制DoubleSummaryStatistics stats = list.stream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
if(stats.getCount() == 0) {
// 处理空集合情况
System.out.println("没有员工数据");
} else {
// 正常显示统计信息
}
由于使用double类型,可能会遇到浮点数精度问题。对于财务等对精度要求高的场景,建议:
BigDecimal进行精确计算如果需要统计DoubleSummaryStatistics不直接支持的数据(如最高工资人数),可以结合其他流操作:
java复制double maxSalary = summary.getMax();
long maxSalaryCount = list.stream()
.filter(e -> e.getSalary() == maxSalary)
.count();
对于大型数据集,可以使用并行流提高处理速度:
java复制DoubleSummaryStatistics stats = list.parallelStream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
注意:并行流不总是更快,对于小数据集可能反而更慢,需要根据实际情况测试。
如果原始数据已经是double类型,直接使用DoubleStream而不是先装箱再拆箱:
java复制// 不好的做法:有额外的装箱开销
List<Double> salaries = ...;
double sum = salaries.stream().mapToDouble(Double::doubleValue).sum();
// 更好的做法:直接使用DoubleStream
double[] salariesArray = ...;
double sum = DoubleStream.of(salariesArray).sum();
如果需要多次统计,可以重用同一个DoubleSummaryStatistics对象:
java复制DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
list1.forEach(e -> stats.accept(e.getSalary()));
list2.forEach(e -> stats.accept(e.getSalary()));
// stats现在包含两个列表的统计结果
结合Collectors.groupingBy可以实现分组统计:
java复制Map<String, DoubleSummaryStatistics> statsByGender = list.stream()
.collect(Collectors.groupingBy(
Employee::getGender,
Collectors.summarizingDouble(Employee::getSalary)
));
statsByGender.forEach((gender, stats) -> {
System.out.println(gender + "员工统计:");
System.out.println(" 人数:" + stats.getCount());
System.out.println(" 平均工资:" + stats.getAverage());
});
对于带有时间属性的数据,可以先按时间段分组再统计:
java复制Map<YearMonth, DoubleSummaryStatistics> monthlyStats = list.stream()
.collect(Collectors.groupingBy(
e -> YearMonth.from(e.getHireDate()),
Collectors.summarizingDouble(Employee::getSalary)
));
结合多个条件进行更复杂的统计分析:
java复制Map<String, Map<String, DoubleSummaryStatistics>> stats = list.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(
Employee::getGender,
Collectors.summarizingDouble(Employee::getSalary)
)
));
问题现象:统计结果与预期不符,特别是最小值和最大值。
可能原因:
解决方案:
java复制DoubleSummaryStatistics stats = list.stream()
.mapToDouble(Employee::getSalary)
.filter(d -> !Double.isNaN(d) && !Double.isInfinite(d))
.summaryStatistics();
DoubleSummaryStatistics本身是线程安全的)问题现象:处理大数据集时速度慢。
优化建议:
问题现象:多次统计后小数部分出现误差。
解决方案:
BigDecimal代替doublejava复制System.out.printf("平均工资:%.2f%n", stats.getAverage());
在实际项目中使用DoubleSummaryStatistics时,我总结了以下几点经验:
groupingBy可以实现强大的多维统计分析对于简单的统计需求,DoubleSummaryStatistics提供了非常方便的解决方案。随着Java语言的演进,流式API和相关的工具类还在不断增强,掌握这些特性可以显著提高开发效率和代码质量。