Java 8 Stream:根据其他收集器定义收集器 [英] Java 8 Stream: Defining collectors based on other collectors

查看:106
本文介绍了Java 8 Stream:根据其他收集器定义收集器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是使用Java 8 Stream API的新手,但我希望将它用于以下问题。假设我有一个名为 InputRecord 的POJO,其中包含 name fieldA fieldB 可以表示以下每行记录的属性:

I'm new to using Java 8 Stream APIs but I'm looking to use it for the following problem. Say I have a POJO called InputRecord containing name, fieldA, and fieldB properties that can represent each row record of the following:

name | fieldA | fieldB
----------------------
A    | 100    | 1.1
A    | 150    | 2.0
B    | 200    | 1.5
A    | 120    | 1.3

InputRecord 看起来像:

public class InputRecord {
    private String name;
    private Integer fieldA;
    private BigDecimal fieldB;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getFieldA() {
        return fieldA;
    }

    public void setFieldA(Integer fieldA) {
        this.fieldA = fieldA;
    }

    public BigDecimal getFieldB() {
        return fieldB;
    }

    public void setFieldB(BigDecimal fieldB) {
        this.fieldB = fieldB;
    }
}

上述四条记录需要合并为两条记录按名称分组,其中:

Those four records above need to be combined into two records grouped by name, where:


  1. 属性 fieldA 汇总

  2. 物业 fieldB 相加

  3. 合并后的记录包括 fieldC 属性,它是乘以 fieldA fieldB 的累积总和的结果。

  1. Property fieldA is summed
  2. Property fieldB is summed
  3. The combined records includes a fieldC property which is the result of multiplying the accumulating sums of both fieldA and fieldB.

因此上述结果将是:

name | sumOfFieldA | sumOfFieldB | fieldC (sumOfFieldA*sumOfFieldB)
-------------------------------------------------------------------
A    | 370         | 4.4         | 1628
B    | 200         | 1.5         | 300

另一个名为 OutputRecord 的POJO代表合并记录的每一行记录:

A different POJO called OutputRecord would represent each row record of the combined records:

public class OutputRecord {
    private String name;
    private Integer sumOfFieldA;
    private BigDecimal sumOfFieldB;
    private BigDecimal fieldC;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getSumOfFieldA() {
        return sumOfFieldA;
    }

    public void setSumOfFieldA(Integer sumOfFieldA) {
        this.sumOfFieldA = sumOfFieldA;
    }

    public BigDecimal getSumOfFieldB() {
        return sumOfFieldB;
    }

    public void setSumOfFieldB(BigDecimal sumOfFieldB) {
        this.sumOfFieldB = sumOfFieldB;
    }

    public BigDecimal getFieldC() {
        return fieldC;
    }

    public void setFieldC(BigDecimal fieldC) {
        this.fieldC = fieldC;
    }
}

转换列表有哪些好方法/解决方案将InputRecords输入到OutputRecords列表中?

What are some good approaches/solutions for transforming a list of InputRecords into a list of OutputRecords?

我看到以下链接是否会有所帮助,但我试图将收集器放入 fieldA fieldB 一起为 fieldC 形成一个新的收藏家:
Java 8 Stream:groupingBy with multiple Collectors

I was seeing if the following link would help but I got stuck trying to put collectors for fieldA and fieldB together in order to form a new collector for fieldC: Java 8 Stream: groupingBy with multiple Collectors

Collector<InputRecord, ?, Integer> fieldACollector = Collectors.summingInt(InputRecord::getFieldA);
Collector<InputRecord, ?, BigDecimal> fieldBCollector = Collectors.reducing(BigDecimal.ZERO, InputRecord::getFieldB, BigDecimal::add);

List<Collector<InputRecord, ?, ?>> collectors = Arrays.asList(fieldACollector, fieldBCollector); // need a fieldCCollector object in the list

收藏家 object将用于创建 complexCollector 对象(根据Tagir Valeev在上面的链接中接受的答案)。

The collectors object would then be used to create a complexCollector object (as per the accepted answer by Tagir Valeev in the above link).

推荐答案

对我来说,最简洁的方法是为此构建一个自定义收集器。这里有多行代码,但您可以在方法下隐藏它,因此您的最终操作将如下所示:

To me the cleanest way is to build a custom collector for that. There are multiple lines of code here, but you can hide it under a method, so your ultimate operation would look like this:

Collection<OutputRecord> output = List.of(first, second, thrid, fourth)
            .stream()
            .parallel()
            .collect(toOutputRecords());

实际的 toOutputRecords 将是:

 private static Collector<InputRecord, ?, Collection<OutputRecord>> toOutputRecords() {
    class Acc {

        Map<String, OutputRecord> map = new HashMap<>();

        void add(InputRecord elem) {
            String value = elem.getName();
            // constructor without fieldC since you compute it at the end
            OutputRecord record = new OutputRecord(value, elem.getFieldA(), elem.getFieldB());
            mergeIntoMap(map, value, record);
        }

        Acc merge(Acc right) {
            Map<String, OutputRecord> leftMap = map;
            Map<String, OutputRecord> rightMap = right.map;

            for (Entry<String, OutputRecord> entry : rightMap.entrySet()) {
                mergeIntoMap(leftMap, entry.getKey(), entry.getValue());
            }
            return this;
        }

        private void mergeIntoMap(Map<String, OutputRecord> map, String value, OutputRecord record) {

            map.merge(value, record, (left, right) -> {
                left.setSumOfFieldA(left.getSumOfFieldA() + right.getSumOfFieldA());
                left.setSumOfFieldB(left.getSumOfFieldB().add(right.getSumOfFieldB()));

                return left;
            });
        }

        public Collection<OutputRecord> finisher() {
            for (Entry<String, OutputRecord> e : map.entrySet()) {
                OutputRecord output = e.getValue();
                output.setFieldC(output.getSumOfFieldB().multiply(BigDecimal.valueOf(output.getSumOfFieldA())));
            }
            return map.values();
        }

    }
    return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}

这篇关于Java 8 Stream:根据其他收集器定义收集器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆