我有一个Settlement类的列表,它具有以下属性:
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
}现在,我想将Settlement的列表按SmartNo (String)分组,并得到settleAmount上的和,这将成为每个SmartNo的新settleAmount。
由于我使用的是Java 8,所以应该使用stream。
使用以下代码,Groupby应该是非常直接的:
Map<String, List<Settlement>> map = list.stream()
.collect(Collectors.groupingBy(Settlement::getSmartNo));
System.out.println(map.getValues());如果我想在按SmartNo分组并在settlementAmount上求和之后得到一个新的列表呢?这里的大多数例子只显示了如何打印这些数字。我感兴趣的是如何获得聚合列表?
发布于 2017-02-12 14:16:59
如果我正确理解这个问题,您需要一个具有自定义合并的toMap收集器,如下所示:
list.stream().collect(Collectors.toMap(
Settlement::getSmartNo,
Function.identity(),
(s1, s2) -> s1.addAmount(s2.getSettleAmount())));在Settlement类中使用helper方法:
Settlement addAmount(double addend) {
this.settleAmount += addend;
return this;
}发布于 2017-02-12 08:31:34
我认为不太复杂的方法是在地图的每个values()成员上创建一个新的流,然后是一个map()和reduce()。我正在映射到一个新的类AggregatedSettlement,其中只有smartNo、volume和settleAmount这三个字段(最后一个字段是和)。然后通过对settleAmount的求和来减少。
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());我对从get()获得的对Optional<AggregatedSettlement>的调用并不太满意;通常您应该避免使用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中可以看出,在Java9中,如果我使用flatMap()代替map(),而使用stream()代替get(),我将能够避免对map()的调用。它将是6个字符多,我可能仍然喜欢它。不过,我还没有尝试过Java9(现在我知道我今天要做什么了:-) get()的优点当然是它会捕获一个编程错误,而内部列表却是空的。
https://stackoverflow.com/questions/42184380
复制相似问题