java8 怎么精简分组求和

数据结构

List<Entity> list = 
[
{name:"冀B1", t1: 20, t2: 30, ……, t12: 23},
{name:"冀B2", t1: 25, t2: 25, ……, t12: 52},
{name:"冀B3", t1: 31, t2: 24, ……, t12: 27},
{name:"冀B4", t1: 25, t2: 31, ……, t12: 29},
……
]

分别求t1,t2……,t12的合计。不是根据name分组,不是根据name分组,不是根据name分组,是求所有数据t1的和,t2的和....t12的和.

结果
{
"t1":123.0,
"t2":250.0,
"t3":430.0,
"t4":0.0,
"t5":0.0,
"t6":65.0,
"t7":953.0,
"t8":0.0,
"t9":0.0,
"t10":0.0,
"t11":0.0,
"t12":0.0
}

我现在是用12次

list.stream().map(Entity::getTx).map(BigDecimal::new).reduce(new BigDecimal("0"), BigDecimal::add).doubleValue()

求和,有没有简单点的方法,没用sum()是因为实际数据有小数精度问题

阅读 4.2k
2 个回答

首先我还是吐吐槽,话说题主你也不给一个期盼的结果,你问题的描述在我看来其实不太清楚你想要什么结果,我指的是具体的返回类型,看了你下面自己的答复,才知道你想要个map,虽然这个可能在你看来无伤大雅,但是我觉得又不是回答什么方案问题,你肯定是希望有一种代码写法,并且可以执行的,那给一个输入+输出的格式不好么,因为只要确认了输入输出,其实起码对于我们帮你想办法的人来说,就可以限定了很多写法和不必要的思考方向了,节约的大家的时间◐▽◑

还是言归正传,说说我自己的想法,这类问题其实我记得我也回答了几个了叭。。。

一般我的思路即: 当没有合适我需求的jdk stream api提供时,都采用自定义Collector的方式解决

题主的输入为List<Entity>,输出为Map<String, Double>,而jdk中提供的返回map的一般也是Collectors.toMapCollectors.groupBy,但是他们都不太符合题主的需求,因此还是自定义Collector的方式比较简单直接,当然题主已经给了一个回答了,在只要完成需求的目标前提下,其实都是可以的,所以我下面这种Collector只是算提供另一个思路吧(如果从使用角度来说,Collector使用起来相对简单)

@RequiredArgsConstructor
public class CustomSumByFieldCollector implements Collector<Entity, Map<String, BigDecimal>, Map<String, BigDecimal>> {

    private final List<String> fieldNames;
    private static final BiFunction<BeanMap, String, BigDecimal> GET_VALUE_FUN = (beanMap, fieldName) -> {
        Double value =  (Double) beanMap.getOrDefault(fieldName, Double.valueOf(0d));
        return new BigDecimal(value);
    };

    @Override
    public Supplier<Map<String, BigDecimal>> supplier() {
        return HashMap::new;
    }

    @Override
    public BiConsumer<Map<String, BigDecimal>, Entity> accumulator() {
        return (map, entity) -> {
            BeanMap beanMap = BeanMap.create(entity);
            fieldNames.forEach(fieldName -> map.merge(fieldName, GET_VALUE_FUN.apply(beanMap, fieldName), BigDecimal::add));
        };
    }

    @Override
    public BinaryOperator<Map<String, BigDecimal>> combiner() {
        return (map1, map2) -> {
            map1.putAll(map2);
            return map1;
        };
    }

    @Override
    public Function<Map<String, BigDecimal>, Map<String, BigDecimal>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
    }
}

上面有用到BeanMapspring-core里的一个工具类,就是把entity转换为map,当然你可以使用其他的转换工具,例如apacheBeanUtils

而使用这个Collector的时候,直接new一个CustomSumByFieldCollector即可

public static void main(String[] args) {
        List<Entity> list = new ArrayList<>();
        List<String> keyList = Arrays.asList("t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "amount");

        Map<String, Double> map = list.stream().collect(new CustomSumByFieldCollector(keyList));
    }

由于题主也没有给测试用例,我也懒得写了哈,见谅,哈哈哈哈(* ̄ω ̄),所以这个代码没有测过,不过思路这样的,一般自定义Collector可以解决100%stream收集问题,起码对我来说100%,哈哈哈,谁用谁说好,如果你对于自定义Collector不太清楚,可以看一下我很早之前的一个说明吧
Java8 collector接口的定制实现

拜了个拜ヾ( ̄▽ ̄)

用反射写了

List<String> keyList = Arrays.asList("t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "amount");
Map<String, Double> map = keyList.stream().collect(Collectors.toMap(e -> e, key -> list.stream().map(i -> {
    try {
        Field field = i.getClass().getDeclaredField(key);
        field.setAccessible(true);
        return Optional.ofNullable((Double) field.get(i)).orElse(0d);
    } catch (Exception e) {
        e.printStackTrace();
        return 0d;
    }
}).map(BigDecimal::new).reduce(BigDecimal.ZERO, BigDecimal::add).doubleValue()));

吐槽,四线小城市的软件公司真完蛋,即使是地头蛇企业

推荐问题
宣传栏