I have a List<MyObject>
class MyObject {
String loanType;
String loanCurrency;
BigDecimal amountPaid;
BigDecimal amountRemaining;
}
And I need to convert this list into a map Map<String, Map<String, MySumObject>
.
I've created a custom MySumObject
class because I need to get a Sum for both amountPaid
and amountRemaining
from the list of MyObject
based on the loanType
and loanCurrency
.
class MySumObject {
BigDecimal paidSum;
BigDecimal remainingSum;
}
Using the code below, I can obtain a 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)
)
));
But I'm stuck on changing it to use MySumObject
instead of BigDecimal
.
Using MySumObject
as an accumulation type would make sense only if all the payment operation represented by MyObject
in the source list belong to different loans.
Otherwise, there's a flow in your logic - remaining amount of a particular loan decreases after each payment. It's pointless to calculate the sum of remaining amounts of the same loan. Instead, we can grab the data regarding remaining amount from the loan object with the latest timestamp or having the lowest remaining amount, but that requires different strategy of grouping and different structure of loan object (MyObject
), i.e. should have at least loanId
.
That said, correctness of your logic - is your responsibility.
Let's get back to the initial idea of accumulating loans having the same currency and loan type into MySumObject
(I assume that you know what you're doing, and it does make sense).
It can be achieved by creating a custom collector based on the 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
with methods addLoan()
and 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
}
Sidenote: it doesn't seem to be justifiable to use a string to represent a currency (unless it's not an assignment requirement) because since Java 1.4 we have a class Currency
.