I have a list of objects and each element of that list is an object that contains several BigDecimal parameters. I'm generating a new Amount object based on some calculations made on the list. It works fine but looks kind of repetitive.
new Amount()
.withAmountIncVat(invoiceLines.stream().map(line -> line.getAmount().getAmountIncVat()).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP))
.withAmount(invoiceLines.stream().map(line -> line.getAmount().getAmount()).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP))
.withVatAmount(invoiceLines.stream().map(line -> line.getAmount().getVatAmount()).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP))
Is there a better way to deal with the lambda expression in order to replace the "repeated" code? You know, improve this function:
invoiceLines.stream().map(line -> line.getAmount().XXX).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP)
In which XXX should be a different BigDecimal attribute from the object each time, and i can use this function for each assignment:
new Amount()
.withVatAmount(function.apply(invoiceLines,<PERHAPS AN ADDITIONAL PARAM>))
.withAmountIncVat(function.apply(invoiceLines,<PERHAPS AN ADDITIONAL PARAM>))
.withAmount(function.apply(invoiceLines,<PERHAPS AN ADDITIONAL PARAM>))
CodePudding user response:
Since operations, you perform in these stream-pipelines differs only with a function extracting BigDecimal
value, to avoid redundancy you can introduce a method expecting a source List
and a function as its arguments.
Note that keeping your functions small and well-readable makes the code more maintainable. And it's also better to have a code that consumes a function and uses it internally than invoking Function.apply()
manually.
That's how the method containing your stream logic might be implemented:
public static <T> BigDecimal reduce(List<T> list,
Function<T, BigDecimal> mapper) {
return list.stream()
.map(mapper)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
}
The code responsible for instantiation and infantilization of the Amount
would look like that:
new Amount()
.withAmountIncVat(reduce(invoiceLines, line -> line.getAmount().getAmountIncVat()))
.withAmount(reduce(invoiceLines, line -> line.getAmount().getAmount()))
.withVatAmount(reduce(invoiceLines, line -> line.getAmount());
CodePudding user response:
Alexander Ivanchenko's answer can be made a bit simpler by making it less generic.
Assuming that line
is an instance of Line
and its getAmount()
method returns an Amount
(replace these as necessary):
private static BigDecimal reduce(List<Line> lines, Function<Amount, BigDecimal> mapper) {
return lines.stream()
.map(Line::getAmount)
.map(mapper)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
}
new Amount()
.withAmountIncVat(reduce(invoiceLines, Amount::getAmountIncVat))
.withAmount(reduce(invoiceLines, Amount::.getAmount))
.withVatAmount(reduce(invoiceLines, Amount::getVatAmount);
CodePudding user response:
I just tried out to create such a reusable function and I think I found a way to do it. I used the BiFunction
interface that defines a function with two inputs. In your case it would be the list as well as a function to extract the needed BigDecimal
from the Amount
object.
The result should be the same as with your original code:
BiFunction<List<Line>, Function<Line, BigDecimal>, BigDecimal> function =
(list, extractor) -> list.stream().map(extractor)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.HALF_UP);
new Amount().withVatAmount(function
.apply(invoiceLines, line -> line.getAmount().getVatAmount()));