'How to Sum up the attribute values of objects in a list having particular IDs and assign it to another object using Streams
My classes.
class MyLoan {
private Long loanId;
private BigDecimal loanAmount;
private BigDecimal totalPaid;
....
}
class Customer {
private Long loanId;
private List<MyLoan> myLoan;
}
I want to iterate over the myLoan from a Customer and calculate the totalPaid amount.
My logic is "If loanId is 23491L or 23492L, then add the loanAmount of those two loanId's and set the value in the totalPaid amount of loanId 23490L".totalPaid amount is always showing as zero with my logic below.
And want to use Java 8 streams, but unable to write multiple conditions when using streams.
BigDecimal spreadAmount;
for (MyLoan myloan: customer.getMyLoan()) {
if (myloan.getLoanId() == 23491L || myloan.getLoanId() == 23492L) {
spreadAmount = spreadAmount.add(myloan.getLoanAmount());
}
if (myloan.getLoanId() == 23490L) {
myloan.setTotalPaid(spreadAmount);
}
}
Solution 1:[1]
The totalPaid field is not modified because your MyLoan instance with id 23490l is encountered before the other two MyLoans.
As @Silvio Mayolo has suggested in the comments you should first compute the total amount with a temp variable and then assign it to the totalPaid field of the MyLoan instance with id 23490l.
This is a stream implementation of what you were trying to do:
//If to make sure that the element MyLoan invoking the setter is actually present
if (myLoan.stream().map(MyLoan::getLoanId).anyMatch(value -> value == 23490l)){
myLoan.stream()
.filter(loan -> loan.getLoanId() == 23490l)
.findFirst()
.get()
.setTotalPaid(myLoan.stream()
.filter(loan -> loan.getLoanId() == 23491l || loan.getLoanId() == 23492l)
.map(MyLoan::getLoanAmount)
.reduce(BigDecimal.valueOf(0), (a, b) -> a = a.add(b)));
}
WARNING
The method get(), invoked on the Optional retrieved with the terminal operation findFirst(), could throw a NoSuchElementException if a MyLoan with id 23490l is not present within the list. You should first make sure that the element is present, as I've done with my if statement.
A second (bad practice) could involve catching the NoSuchElementException thrown by the get(), in case the desired MyLoan is not present. As it has been pointed out in the comments, catching a RuntimeException (NoSuchElementException is a subclass of it) is a bad practice, as we should investigate on the origin of the problem rather than simply catching the exception. This second approach was honestly a (lazy) last resort only to show another possible way of handling the case.
Solution 2:[2]
Firstly, you need to fetch a loan for which you want to define a total paid amount. If this step succeeds, then calculate a total.
In order to find a loan with a particular id using streams, you need to create a stream over the customers loans and apply filter() in conjunction with findFirst() on it. It'll give you the first element from the stream that matches the predicate passed into the filter. Because result might not be present in the stream, findFirst() returns an Optional object.
Optional class offers a wide range of method to interact with it like orElse(), ifPresent(), orElse(), etc. Avoid blindly using get(), unless you didn't check that value is present, which is in many cases isn't the most convenient way to deal with it. Like in the code below, ifPresent() is being used to proceed with the logic if value is present.
So if the required loan was found, the next step is to calculate the total. Which is done by filtering out target ids, extracting amount by applying map() and adding the amounts together using reduce() as a terminal operation.
public static void setTotalPaid(Customer customer, Long idToSet, Long... idsToSumUp) {
List<MyLoan> loans = customer.getMyLoan();
getLoanById(loans, idToSet).ifPresent(loan -> loan.setTotalPaid(getTotalPaid(loans, idsToSumUp)));
}
public static Optional<MyLoan> getLoanById(List<MyLoan> loans, Long id) {
return loans.stream()
.filter(loan -> loan.getLoanId().equals(id))
.findFirst();
}
public static BigDecimal getTotalPaid(List<MyLoan> loans, Long... ids) {
Set<Long> targetLoans = Set.of(ids); // wrapping with set to improve performance
return loans.stream()
.filter(loan -> targetLoans.contains(loan.getLoanId()))
.map(MyLoan::getLoanAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
