Java8流:基于其他收集器定义收集器
我不熟悉使用Java8流API,但我希望使用它解决以下问题。假设我有一个名为InputRecord的POJO,其中包含name、fieldA和fieldB属性,这些属性可以表示以下每一行记录:Java8流:基于其他收集器定义收集器,java,java-8,java-stream,Java,Java 8,Java Stream,我不熟悉使用Java8流API,但我希望使用它解决以下问题。假设我有一个名为InputRecord的POJO,其中包含name、fieldA和fieldB属性,这些属性可以表示以下每一行记录: name | fieldA | fieldB ---------------------- A | 100 | 1.1 A | 150 | 2.0 B | 200 | 1.5 A | 120 | 1.3 InputRecord看起来像: public cl
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;
}
}
上述四条记录需要合并成两条按名称分组的记录,其中:
属性字段A被求和
属性字段B求和
组合记录包括一个fieldC属性,该属性是fieldA和fieldB累加和的乘积。
因此,上述结果为:
name | sumOfFieldA | sumOfFieldB | fieldC (sumOfFieldA*sumOfFieldB)
-------------------------------------------------------------------
A | 370 | 4.4 | 1628
B | 200 | 1.5 | 300
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);
}
称为OutputRecord的不同POJO将表示组合记录的每一行记录:
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;
}
}
将inputrecord列表转换为outputrecord列表有哪些好的方法/解决方案
我正在查看以下链接是否有帮助,但我在尝试将fieldA和fieldB的收集器放在一起以形成fieldC的新收集器时遇到了困难:
然后,收集器对象将用于根据Tagir Valeev在上述链接中接受的答案创建complexCollector对象。您可以使用组合和聚合函数从输入记录列表生成输出记录列表
Map<String, OutputRecord> result = inputRecords.stream().collect(() -> new HashMap<>(),
(HashMap<String, OutputRecord> map, InputRecord inObj) -> {
OutputRecord out = map.get(inObj.getName());
if (out == null) {
out = new OutputRecord();
out.setName(inObj.getName());
out.setSumOfFieldA(inObj.getFieldA());
out.setSumOfFieldB(inObj.getFieldB());
} else {
Integer s = out.getSumOfFieldA();
out.setSumOfFieldA(s + inObj.getFieldA());
BigDecimal bd = out.getSumOfFieldB();
out.setSumOfFieldB(bd.add(inObj.getFieldB()));
}
out.setFieldC(out.getSumOfFieldB().multiply(new BigDecimal(out.getSumOfFieldA())));
map.put(out.getName(), out);
}, (HashMap<String, OutputRecord> out1, HashMap<String, OutputRecord> out2) -> {
out1.putAll(out2);
});
System.out.println(result);
对我来说,最干净的方法就是为此构建一个自定义收集器。这里有多行代码,但您可以将其隐藏在方法下,因此您的最终操作如下所示:
Collection<OutputRecord> output = List.of(first, second, thrid, fourth)
.stream()
.parallel()
.collect(toOutputRecords());
你可以使用Stream.reduce。。将两条记录转换为一条记录。它创建了一组需要JVM进行垃圾收集的临时对象
Collection<InputRecord> input = Arrays.asList(
new InputRecord("A", 100, new BigDecimal(1.1)),
new InputRecord("A", 150, new BigDecimal(2.0)),
new InputRecord("B", 200, new BigDecimal(1.5)),
new InputRecord("A", 120, new BigDecimal(1.3)));
Collection<OutputRecord> output = input.stream()
// group records for particular Name into a List
.collect(Collectors.groupingBy(InputRecord::getName))
.values().stream()
// Reduce every List to a single records, performing summing
.map(records -> records.stream()
.reduce((a, b) ->
new InputRecord(a.getName(),
a.getFieldA() + b.getFieldA(),
a.getFieldB().add(b.getFieldB()))))
.filter(Optional::isPresent)
.map(Optional::get)
// Finally transform the InputRecord to OutputRecord
.map(record -> new OutputRecord(record.getName(),
record.getFieldA(),
record.getFieldB(),
record.getFieldB().multiply(new BigDecimal(record.getFieldA()))))
.collect(Collectors.toList());
与其定义定制的收集器(我认为这很复杂,也很难维护),不如使用一种通用的实用方法来组合多个收集器。例如:
public static <T, A1, A2, R1, R2> Collector<T, Tuple2<A1, A2>, Tuple2<R1, R2>> combine(final Collector<? super T, A1, R1> collector1,
final Collector<? super T, A2, R2> collector2) {
...
}
为什么你需要一个fieldC,而你可以在运行中计算它?你能提供更多关于你的POJO的信息吗?它是可变的吗?它有吸气剂吗?构造函数是什么样子的?谢谢,我添加了更多信息。POJO可以是可变的多亏了Holger,我添加了一个实际的问题:OP不需要映射,您至少可以返回result.values,或者使用一个更可读和可移植的解决方案,比如:您可能可以在这里使用标识InputRecord0,BigDecimla.ZERO,而不是filterOptional::isPresent.mapOptional::get,另外,可变收集器在这里更可取,因为它创建的对象更少。为了使这更美观,您可以再创建两个方法:InputRecord joinputrecord left、InputRecord right。。。以及一个或多个形式为OutputRecord transform InputRecord in。因此,该管道将变得更短,可读性更强。还是加一
public static <T, A1, A2, R1, R2> Collector<T, Tuple2<A1, A2>, Tuple2<R1, R2>> combine(final Collector<? super T, A1, R1> collector1,
final Collector<? super T, A2, R2> collector2) {
...
}
Collector<InputRecord, ?, Integer> fieldACollector = MoreCollectors.summingInt(InputRecord::getFieldA);
Collector<InputRecord, ?, BigDecimal> fieldBCollector = MoreCollectors.reducing(BigDecimal.ZERO, InputRecord::getFieldB, BigDecimal::add);
inputRecords.stream().collect(MoreCollectors.groupingBy(InputRecord::getName,
MoreCollectors.combine(fieldACollector, fieldBCollector)))
.entrySet().stream()
.map(e -> new OutputRecord(e.getKey(), e.getValue()._1, e.getValue()._2))
.collect(Collectors.toList());
StreamEx.of(inputRecords)
.groupBy(InputRecord::getName, MoreCollectors.combine(fieldACollector, fieldBCollector))
.map(e -> new OutputRecord(e.getKey(), e.getValue()._1, e.getValue()._2)).toList();