Java 8 Stream:根据其他收集器定义收集器 [英] Java 8 Stream: Defining collectors based on other collectors
问题描述
我是使用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:
- 属性
fieldA
汇总 - 物业
fieldB
相加 - 合并后的记录包括
fieldC
属性,它是乘以fieldA
和fieldB
的累积总和的结果。
- Property
fieldA
is summed - Property
fieldB
is summed - The combined records includes a
fieldC
property which is the result of multiplying the accumulating sums of bothfieldA
andfieldB
.
因此上述结果将是:
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屋!