如何在 groupby 后获取新列表并对一个属性求和
How to obtain new list after groupby and sum over one attribute
我有一个 Settlement
class 的列表,它具有以下属性:
public class Settlement {
private String contractNo;
private String smartNo;
private String dealTrackNo;
private String buySellFlag;
private String cashFlowType;
private String location;
private String leaseNo;
private String leaseName;
private double volume;
private double price;
private double settleAmount;
// getters and setters
}
现在我想按 SmartNo
(字符串)对 Settlement
的列表进行分组,并得到 settleAmount
的总和,这成为每个 settleAmount
的新 settleAmount
=14=].
因为我使用的是 Java 8,所以 stream
应该是正确的选择。
Groupby 使用以下代码应该非常简单:
Map<String, List<Settlement>> map = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo));
System.out.println(map.getValues());
如果我想通过SmartNo分组并通过settlementAmount求和得到一个新列表怎么办?那里的大多数例子只展示了如何打印出总和。我感兴趣的是如何获取聚合列表?
我认为 not-too-complex 方式是在地图 values()
的每个成员上创建一个新流,然后是 map()
和 reduce()
。我正在映射到一个新的 class AggregatedSettlement
,只有三个字段 smartNo
、volume
和 settleAmount
(最后一个是总和)。然后通过对 settleAmount
s.
求和来减少
List<AggregatedSettlement> aggregatedList = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo))
.values()
.stream()
.map(innerList -> innerList.stream()
.map(settlm -> new AggregatedSettlement(settlm.getSmartNo(),
settlm.getVolume(), settlm.getSettleAmount()))
.reduce((as1, as2) -> {
if (as1.getVolume() != as2.getVolume()) {
throw new IllegalStateException("Different volumes " + as1.getVolume()
+ " and " + as2.getVolume() + " for smartNo " + as1.getSmartNo());
}
return new AggregatedSettlement(as1.getSmartNo(), as1.getVolume(),
as1.getSettleAmount() + as2.getSettleAmount());
})
.get()
)
.collect(Collectors.toList());
我对 reduce()
在 Optional<AggregatedSettlement>
上打给 get()
的电话不太满意;通常你应该避免 get()
。在这种情况下,我知道原始分组只生成至少一个元素的列表,因此 reduce()
不能给出一个空的可选值,因此对 get()
的调用将起作用。可能的改进是 orElseThrow()
和一个更具解释性的异常。
我相信还有优化的空间。我最终生成的 AggregatedSettlement
个对象确实比我们需要的多得多。一如既往,在你知道你需要之前不要优化。
编辑:如果只是为了练习,这里是不构造多余 AggregatedSettlement
对象的版本。相反,它会在地图的每个列表上创建 两个 流,并且长 5 行:
List<AggregatedSettlement> aggregatedList = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo))
.entrySet()
.stream()
.map(entry -> {
double volume = entry.getValue()
.stream()
.mapToDouble(Settlement::getVolume)
.reduce((vol1, vol2) -> {
if (vol1 != vol2) {
throw new IllegalStateException("Different volumes " + vol1
+ " and " + vol2 + " for smartNo " + entry.getKey());
}
return vol1;
})
.getAsDouble();
double settleAmountSum = entry.getValue()
.stream()
.mapToDouble(Settlement::getSettleAmount)
.sum();
return new AggregatedSettlement(entry.getKey(), volume, settleAmountSum);
})
.collect(Collectors.toList());
选择您认为更容易阅读的那个。
编辑 2:从 this answer 看来,在 Java 9 中,如果我使用 [= 而不是 map()
,我将能够避免调用 Optional.get()
31=] 而不是 get()
我使用 stream()
。它将长 6 个字符,我可能仍然更喜欢它。不过,我还没有尝试过 Java 9(现在我知道我今天要做什么了:-) get()
的优点当然是它会在内部捕获编程错误列表是空的。
如果我对问题的理解正确,您需要一个 toMap
具有自定义合并功能的收集器,如下所示:
list.stream().collect(Collectors.toMap(
Settlement::getSmartNo,
Function.identity(),
(s1, s2) -> s1.addAmount(s2.getSettleAmount())));
里面有辅助方法 Settlement
class:
Settlement addAmount(double addend) {
this.settleAmount += addend;
return this;
}
我有一个 Settlement
class 的列表,它具有以下属性:
public class Settlement {
private String contractNo;
private String smartNo;
private String dealTrackNo;
private String buySellFlag;
private String cashFlowType;
private String location;
private String leaseNo;
private String leaseName;
private double volume;
private double price;
private double settleAmount;
// getters and setters
}
现在我想按 SmartNo
(字符串)对 Settlement
的列表进行分组,并得到 settleAmount
的总和,这成为每个 settleAmount
的新 settleAmount
=14=].
因为我使用的是 Java 8,所以 stream
应该是正确的选择。
Groupby 使用以下代码应该非常简单:
Map<String, List<Settlement>> map = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo));
System.out.println(map.getValues());
如果我想通过SmartNo分组并通过settlementAmount求和得到一个新列表怎么办?那里的大多数例子只展示了如何打印出总和。我感兴趣的是如何获取聚合列表?
我认为 not-too-complex 方式是在地图 values()
的每个成员上创建一个新流,然后是 map()
和 reduce()
。我正在映射到一个新的 class AggregatedSettlement
,只有三个字段 smartNo
、volume
和 settleAmount
(最后一个是总和)。然后通过对 settleAmount
s.
List<AggregatedSettlement> aggregatedList = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo))
.values()
.stream()
.map(innerList -> innerList.stream()
.map(settlm -> new AggregatedSettlement(settlm.getSmartNo(),
settlm.getVolume(), settlm.getSettleAmount()))
.reduce((as1, as2) -> {
if (as1.getVolume() != as2.getVolume()) {
throw new IllegalStateException("Different volumes " + as1.getVolume()
+ " and " + as2.getVolume() + " for smartNo " + as1.getSmartNo());
}
return new AggregatedSettlement(as1.getSmartNo(), as1.getVolume(),
as1.getSettleAmount() + as2.getSettleAmount());
})
.get()
)
.collect(Collectors.toList());
我对 reduce()
在 Optional<AggregatedSettlement>
上打给 get()
的电话不太满意;通常你应该避免 get()
。在这种情况下,我知道原始分组只生成至少一个元素的列表,因此 reduce()
不能给出一个空的可选值,因此对 get()
的调用将起作用。可能的改进是 orElseThrow()
和一个更具解释性的异常。
我相信还有优化的空间。我最终生成的 AggregatedSettlement
个对象确实比我们需要的多得多。一如既往,在你知道你需要之前不要优化。
编辑:如果只是为了练习,这里是不构造多余 AggregatedSettlement
对象的版本。相反,它会在地图的每个列表上创建 两个 流,并且长 5 行:
List<AggregatedSettlement> aggregatedList = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo))
.entrySet()
.stream()
.map(entry -> {
double volume = entry.getValue()
.stream()
.mapToDouble(Settlement::getVolume)
.reduce((vol1, vol2) -> {
if (vol1 != vol2) {
throw new IllegalStateException("Different volumes " + vol1
+ " and " + vol2 + " for smartNo " + entry.getKey());
}
return vol1;
})
.getAsDouble();
double settleAmountSum = entry.getValue()
.stream()
.mapToDouble(Settlement::getSettleAmount)
.sum();
return new AggregatedSettlement(entry.getKey(), volume, settleAmountSum);
})
.collect(Collectors.toList());
选择您认为更容易阅读的那个。
编辑 2:从 this answer 看来,在 Java 9 中,如果我使用 [= 而不是 map()
,我将能够避免调用 Optional.get()
31=] 而不是 get()
我使用 stream()
。它将长 6 个字符,我可能仍然更喜欢它。不过,我还没有尝试过 Java 9(现在我知道我今天要做什么了:-) get()
的优点当然是它会在内部捕获编程错误列表是空的。
如果我对问题的理解正确,您需要一个 toMap
具有自定义合并功能的收集器,如下所示:
list.stream().collect(Collectors.toMap(
Settlement::getSmartNo,
Function.identity(),
(s1, s2) -> s1.addAmount(s2.getSettleAmount())));
里面有辅助方法 Settlement
class:
Settlement addAmount(double addend) {
this.settleAmount += addend;
return this;
}