在 Java 8 中使用自定义对象作为缩减类型创建地图的地图



>我有一个List<MyObject>

class MyObject {
String loanType;
String loanCurrency;
BigDecimal amountPaid;
BigDecimal amountRemaining;
}

我需要将此列表转换为地图Map<String, Map<String, MySumObject>.

我创建了一个自定义MySumObject类,因为我需要根据loanTypeloanCurrencyMyObject列表中获取amountPaidamountRemaining的总

class MySumObject {
BigDecimal paidSum;
BigDecimal remainingSum;
}

使用下面的代码,我可以获得一个Map<String,Map<String, BigDecimal>>

Map<String, Map<String, BigDecimal>> result = list1.stream().collect(
Collectors.groupingBy(LoanObject::getLoanType,
Collectors.groupingBy(LoanObject::getLoanCurrency,
Collectors.reducing(
BigDecimal.ZERO,
LoanObject::getAmountPaid,
BigDecimal::add)
)
));

但是我坚持将其更改为使用MySumObject而不是BigDecimal.

仅当源列表中MyObject表示的所有付款操作都属于不同的贷款时,使用MySumObject作为累积类型才有意义。

否则,您的逻辑中会出现一个流程 - 每次付款后,特定贷款的剩余金额都会减少。计算同一笔贷款的剩余金额之和是没有意义的。相反,我们可以从具有最新时间戳或具有最低剩余金额的贷款对象中获取有关剩余金额的数据,但这需要不同的分组策略和不同的贷款对象结构(MyObject),即至少应该有loanId

也就是说,你的逻辑的正确性 - 是你的责任。

让我们回到最初的想法,将具有相同货币和贷款类型的贷款累积到MySumObject中(我假设你知道你在做什么,这确实是有道理的)。

这可以通过基于以下MySumObject创建自定义收集器来实现:

Map<String, Map<String, MySumObject>> result = list1.stream()
.collect(Collectors.groupingBy(
MyObject::getLoanType,
Collectors.groupingBy(
MyObject::getLoanCurrency,
Collector.of(
MySumObject::new,
MySumObject::addLoan,
MySumObject::merge
))
));

MySumObject方法addLoan()merge()

public static class MySumObject {
private BigDecimal paidSum = BigDecimal.ZERO;
private BigDecimal remainingSum = BigDecimal.ZERO;

public void addLoan(LoanObject loan) {
paidSum = paidSum.add(loan.getAmountPaid());
remainingSum = remainingSum.add(loan.getAmountRemaining());
}

public MySumObject merge(MySumObject other) {
paidSum = paidSum.add(other.getPaidSum());
remainingSum = remainingSum.add(other.getRemainingSum());
return this;
}

// getters, constructor, etc. are omitted
}

旁注:使用字符串来表示货币似乎不合理(除非它不是赋值要求),因为从 Java 1.4 开始,我们有一个类Currency

通过groupingBy创建嵌套映射后,您通常会得到一个MyObject列表。 但是您想对这些值求和,因此您需要将CollectingAndThenfinisher一起使用。 在这种情况下,整理器会流式传输每个列表并应用缩减来构建最终对象。在我的解决方案中,我每次都创建一个新MySumObject,将之前计算的 MySumObject 添加到列表中的下一个MyObject值。此方法非常常见,并且具有优点,因为它也适用于不可变容器(如records),并且不需要在用于检索字段的标准 getter 之外对类进行额外修改。为了避免混乱,我创建了一个帮助程序方法来执行缩减。但是实际的求和代码本可以放在收集器中。

Map<String, Map<String, MySumObject>> map = list.stream()
.collect(Collectors.groupingBy(
MyObject::getLoanType,
Collectors.groupingBy(
MyObject::getLoanCurrency,
Collectors.collectingAndThen(
Collectors.toList(),
lst->summing(lst)))));

将列表缩减为单个对象的帮助程序方法。 由于 reduce 适用于两种不同的类型,因此使用了 reduce 的三参数版本。 首先,目标MySumObject初始化为零和。然后,缩减将创建新MySumObject,并向其中添加每个适当的MyObject。第三个参数将用于并行处理,以对来自不同线程的不同MySumObjects求和。 在这种情况下,我只是分配了已经计算的值,假设单个流。

public static MySumObject summing(List<MyObject> list) {
return list.stream()
.reduce(new MySumObject(
BigDecimal.ZERO,
BigDecimal.ZERO),
(MySumObject mySum, MyObject myOb) -> new MySumObject(
myOb.getAmountPaid()
.add(mySum.getPaidSum()),
myOb.getAmountRemaining()
.add(mySum.getRemainingSum())),(a,b)->a);
}

作为替代方案,您也可以这样做,我相信这会更有效,因为您在引用每个MyObject时求和。 它利用了Java 8+地图增强功能。 如果缺少键,则 ComputeIfAbsent 创建值。 它将返回该值或现有值。 由于该值也是映射,因此可以应用计算。 如果缺少键或处理现有值,这也将创建值。

Map<String, Map<String, MySumObject>> map = new HashMap<>();
for (MyObject mo : list) {
map.computeIfAbsent(mo.getLoanType(),
v -> new HashMap<String, MySumObject>())
.compute(mo.getLoanCurrency(),
(k, v) -> v == null ?
new MySumObject(
mo.getAmountPaid(),
mo.getAmountRemaining()) :
new MySumObject(
v.getPaidSum().add(mo
.getAmountPaid()),
v.getRemainingSum().add(mo
.getAmountRemaining())));
}

对于以下数据,将显示输出,并且两种方法的输出相同。

public static void main(String[] args) {
List<MyObject> list = List.of(
new MyObject("Type1", "Currency1",
BigDecimal.valueOf(10),
BigDecimal.valueOf(100)),
new MyObject("Type2", "Currency2",
BigDecimal.valueOf(20),
BigDecimal.valueOf(200)),
new MyObject("Type1", "Currency1",
BigDecimal.valueOf(30),
BigDecimal.valueOf(300)),
new MyObject("Type2", "Currency2",
BigDecimal.valueOf(40),
BigDecimal.valueOf(400)));
map.entrySet().forEach(System.out::println);

指纹

Type2={Currency2=MySumObject[getPaidSum=60, getRemainingSum=600]}
Type1={Currency1=MySumObject[getPaidSum=40, getRemainingSum=400]}

最新更新