Home > database >  Improve Map/Reduce lambda expression by using a different BigDecimal attribute each time
Improve Map/Reduce lambda expression by using a different BigDecimal attribute each time

Time:09-22

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()));
  • Related