java如何使用groupBy创建一个值为BigDecimal字段平均值的映射?
我有以下课程:
final public class Person {
private final String name;
private final String state;
private final BigDecimal salary;
public Person(String name, String state, BigDecimal salary) {
this.name = name;
this.state = state;
this.salary = salary;
}
//getters omitted for brevity...
}
我想创建一个地图,列出各州的平均工资。如何使用Java8流来实现这一点?我试图在groupBy上使用下游收集器,但未能以优雅的方式做到这一点
我做了以下工作,但看起来很可怕:
Stream.of(p1,p2,p3,p4).collect(groupingBy(Person::getState, mapping(d -> d.getSalary(), toList())))
.forEach((state,wageList) -> {
System.out.print(state+"-> ");
final BigDecimal[] wagesArray = wageList.stream()
.map(bd -> new BigDecimal[]{bd, BigDecimal.ONE})
.reduce((a, b) -> new BigDecimal[]{a[0].add(b[0]), a[1].add(BigDecimal.ONE)})
.get();
System.out.println(wagesArray[0].divide(wagesArray[1])
.setScale(2, RoundingMode.CEILING));
});
有更好的办法吗
# 1 楼答案
下面是一个仅使用BigDecimal算法的完整示例,展示了如何实现自定义收集器
如果您接受将BigDecimal值转换为double(这对于平均工资来说是完全可以接受的,IMHO),那么您可以使用
# 2 楼答案
如果您需要
BigDecimal
精度,并且不介意额外的迭代,您可以这样做:# 3 楼答案
这里有一种方法。尽管我仍然相信还有其他更好的方法
# 4 楼答案
注意:这不是最好的解决方案。请阅读下面的(编辑部分)以了解更好的方法
您可以使用^{} 将每个州的工资累积到
ArrayList
中,然后使用finisher函数计算每个州的平均值:这种方法不会失去精度,因为计算每个状态的平均值的每个操作都是在
BigDecimal
实例上执行的编辑:正如@Holger在他的评论中指出的,这不是解决问题的最佳方法,因为根本不需要使用
ArrayList
存储所有BigDecimal
实例来计算平均值。相反,使用BigDecimal
来累加部分和,使用long
来累加计数就足够了(这是@JB Nizet在他的回答中遵循的方法,这里我修改了一些小细节)以下是一个考虑到这些因素的修改版本:
Acc
是用于累积部分结果(部分和和和计数)的类现在,我们可以在这个类中使用
Collector.of
:或者更好的是,我们可以声明一个助手方法,其中
Acc
类是本地类:现在可以按如下方式使用此方法: