Accumulate inside forEach Java 8 - java

How can I accumulate inside a forEach? this is my code:
public Double accumulate() {
Double accum = 0d;
numbers.stream().forEach(p-> {
// if condition
accum = accum + numbers.getAmount();
// else
accum = accum + numbers.getAmountWithInterest();
});
return accum;
}
Maybe I should use map instead of forEach, I tried a couple of things but it didn't work. Thanks

I do not think it is a good idea to make side effect when using lambda. It is a bad way of mixing functional and imperative programming. You can do it easier by
numbers.stream().mapToInt(p-> {
// if condition
return numbers.getAmount();
// else
return numbers.getAmountWithInterest();
}).sum();

You can use mapToDouble and sum instead of forEach with Java Streams:
#Test
public void sumOfDoubleWithStreams()
{
List<Amount> numbers =
Arrays.asList(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
double sum = numbers.stream()
.mapToDouble(
amount -> amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount())
.sum();
Assert.assertEquals(64.0d, sum, 0.0d);
}
If you really want to use forEach, you need to have an object that can be mutated inside of the forEach call. You can use AtomicDouble here, or another class like DoubleSummaryStatistics or your own accumulating class.
#Test
public void sumOfDoubleWithForEach()
{
List<Amount> numbers =
Arrays.asList(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
AtomicDouble sum = new AtomicDouble();
numbers.forEach(amount ->
sum.addAndGet(amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount()));
Assert.assertEquals(64.0d, sum.get(), 0.0d);
}
If you are open to using a third-party library, you can use sumOfDouble on any container from Eclipse Collections.
#Test
public void sumOfDouble()
{
MutableList<Amount> numbers =
Lists.mutable.with(new Amount(10.0, 0.0), new Amount(20.0, 0.05), new Amount(30.0, 0.1));
double sum = numbers.sumOfDouble(
amount -> amount.hasInterest() ? amount.getAmountWithInterest() : amount.getAmount());
Assert.assertEquals(64.0d, sum, 0.0d);
}
I created an Amount class to hold the amount and interest rate:
public static final class Amount
{
private final double amount;
private final double interest;
public Amount(double amount, double interest)
{
this.amount = amount;
this.interest = interest;
}
public double getAmountWithInterest()
{
return this.amount * (1.0 + this.interest);
}
public boolean hasInterest()
{
return this.interest > 0.0d;
}
public double getAmount()
{
return amount;
}
}
Note: I am a committer for Eclipse Collections.

Related

Passing a method reference as parameter

In a case like this:
public class Order {
List<Double> prices = List.of(1.00, 10.00, 100.00);
List<Double> pricesWithTax = List.of(1.22, 12.20, 120.00);
Double sumBy(/* method reference */) {
Double sum = 0.0;
for (Double price : /* method reference */) {
sum += price;
}
return sum;
}
public List<Double> getPrices() { return prices; }
public List<Double> getPricesWithTax() { return pricesWithTax; }
}
how can I declare the sumBy method in a way that can be called like this:
Order order = new Order();
var sum = order.sumBy(order::getPrices);
var sumWithTaxes = order.sumBy(order::getPricesWithTax);
I'm not using the Java 8 API for the sum because my goal is only to understand how pass a method reference.
Your 2 methods take no argument and return an object, so that fits the Supplier.get() method.
Don't use Double for the sum variable, since that will auto-box and auto-unbox way too much.
Method can be static since it doesn't use any fields or other methods of the class.
static double sumBy(Supplier<List<Double>> listGetter) {
double sum = 0.0;
for (double price : listGetter.get()) {
sum += price;
}
return sum;
}
Better yet:
static double sumBy(Supplier<List<Double>> listGetter) {
return listGetter.get().stream().mapToDouble(Double::doubleValue).sum();
}
You seem to want a Supplier like
Double sumBy(Supplier<List<Double>> f) {
Double sum = 0.0;
for (Double price : f.get()) {
sum += price;
}
return sum;
}
Your List.of syntax was giving me errors. So I did
List<Double> prices = Arrays.asList(1.00, 10.00, 100.00);
List<Double> pricesWithTax = Arrays.asList(1.22, 12.20, 120.00);
Then I tested like
public static void main(String[] args) throws IOException {
Order order = new Order();
double sum = order.sumBy(order::getPrices);
double sumWithTaxes = order.sumBy(order::getPricesWithTax);
System.out.printf("%.2f %.2f%n", sum, sumWithTaxes);
}
Outputs
111.00 133.42
I think the Supplier<T> functional interface is what you’re looking for:
Double sumBy(Supplier<Collection<Double>> supplier) {
Collection<Double> prices = supplier.get();
}
Use Double sumBy(Supplier<List<Double>> doubles)

working with streams problem of Local variable defined in an enclosing scope must be final

i have this algorithm that i want to change it with parallel streams so it makes less time for the calculation, but when i did it i got the error Local variable defined in an enclosing scope must be final for realiseValeur, nbValid and invalidite.So how can i work with parelle streams in this algorithm.
This is my algorithm in which i want to work with parallelstreams :
#Override
public Map<String, Double> getMapRealise(Date date, String code, Long pc) {
Map<String, Double> map = new HashMap<>();
List<Period> periodList = this.getListPeriod(date, code);
Double realiseValeur = 0.0;
Double invalidite = 0.0;
if (periodList != null) {
for (Period period : periodList) {
Double periode = this.getResolutionTraduiteEnHeures(period.getResolution().getV());
// Date dateDebutPrevisionnel =
// this.getDateDebutPrevisionnel(period.getTimeInterval().getV());
Double nbValid = 0.0;
for (Pt pt : period.getListPt()) {
realiseValeur += periode * pt.getQ().getV() / pcnTranche / NBR_HEURES_PAR_JOURS;
nbValid = nbValid + pt.getCq().getV();
}
if ((nbValid * periode) < NBR_HEURES_MINE_PAR_JOURS) {
invalidite++;
}
}
}
else {
LOGGER.warn( "n existe pas ");
}
map.put(REALISE_VALEUR, realiseValeur);
map.put(REALISE_INVALIDITE, invalidite);
return map;}
I tried this but i got the error Local variable defined in an enclosing scope must be final for realiseValeur, nbValid and invalidite:
#Override
public Map<String, Double> getMapRealise(Date date, String code, Long pc) {
Map<String, Double> map = new HashMap<>();
List<Period> periodList = this.getListPeriod(date, code);
Double realiseValeur = 0.0;
Double invalidite = 0.0;
if (periodList != null) {
periodList.parallelStream().forEach(period -> {
Double periode = this.getResolutionTraduiteEnHeures(period.getResolution().getV());
// Date dateDebutPrevisionnel =
// this.getDateDebutPrevisionnel(period.getTimeInterval().getV());
Double nbValid = 0.0;
period.getListPt().parallelStream().forEach(pt -> {
realiseValeur += periode * pt.getQ().getV() / pcnTranche / NBR_HEURES_PAR_JOURS;
nbValid = nbValid + pt.getCq().getV();
});
if ((nbValid * periode) < NBR_HEURES_MINE_PAR_JOURS) {
invalidite++;
}
});
}
else {
LOGGER.warn("n existe pas ");
}
map.put(REALISE_VALEUR, realiseValeur);
map.put(REALISE_INVALIDITE, invalidite);
return map;
}
You need to return something from each iteration of the outer loop which has the incremental values for each of the iterations.
Stream<Map<String, Double>> fromParallel =
periodList.parallelStream()
.map(period -> {
double realiseValeurInc = 0.0;
double invaliditeInc = 0.0;
// Increment these variables instead of realiseValeur and invalidite
return Map.of(REALISE_VALEUR, realiseValeurInc, REALISE_INVALIDITE, invaliditeInc);
});
Now just merge all of these maps together:
Map<String, Double> result =
fromParallel.reduce(
(a, b) -> Map.of(
REALISE_VALEUR, a.get(REALISE_VALEUR) + b.get(REALISE_VALEUR),
REALISE_INVALIDITE, a.get(REALISE_INVALIDITE) + b.get(REALISE_INVALIDITE)));
(You don't have to do this in two separate steps, that's merely to explain the process.)
And you can use this as the return value (or copy this to a HashMap, if that's what you really need).
Of course, this is wildly inefficient, because of all the allocation of both Double and Map. It will probably be slower than the existing loop-based code. But it will at least give you the correct answer (eventually).
You can use AtomicInteger for this operation. since you are using Double for your opeartions. create your own AtomicFloat and use it instead of Double and your problem will be solved :)
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Float.*;
class AtomicFloat extends Number {
private AtomicInteger bits;
public AtomicFloat() {
this(0f);
}
public AtomicFloat(float initialValue) {
bits = new AtomicInteger(floatToIntBits(initialValue));
}
public final boolean compareAndSet(float expect, float update) {
return bits.compareAndSet(floatToIntBits(expect),
floatToIntBits(update));
}
public final void set(float newValue) {
bits.set(floatToIntBits(newValue));
}
public final float get() {
return intBitsToFloat(bits.get());
}
public float floatValue() {
return get();
}
public final float getAndSet(float newValue) {
return intBitsToFloat(bits.getAndSet(floatToIntBits(newValue)));
}
public final boolean weakCompareAndSet(float expect, float update) {
return bits.weakCompareAndSet(floatToIntBits(expect),
floatToIntBits(update));
}
public double doubleValue() { return (double) floatValue(); }
public int intValue() { return (int) get(); }
public long longValue() { return (long) get(); }
}

How to aggregate multiple fields using Collectors in Java

I have a custom Object Itemized which has two fields amount and tax. I have an array of Itemized objects and I am looking to sum the two fields in the same stream. Below is how I am calculating the sum of both the fields.
double totalAmount = Arrays.stream(getCharges()).map(Itemized::getAmount).reduce(0.0, Double::sum));
double totalTax = Arrays.stream(getCharges()).map(Itemized::getTax).reduce(0.0, Double::sum));
Is there any way I don't have to parse the stream two times and can sum the two fields in one go ? I am not looking to sum totalTax and totalAmount but want their sum separately. I was looking at Collectors but was not able to find any example which would allow aggregating of multiple fields in one go.
use a for loop ?
double taxSum = 0;
double amountSum = 0;
for (Itemized itemized : getCharges()) {
taxSum += itemized.getTax();
amountSum += itemized.getAmount();
}
You can try to use the teeing Collector, like so:
Arrays.stream(getCharges()) // Get the charges as a stream
.collect(Collectors // collect
.teeing( // both of the following:
Collectors.summingDouble(Itemized::getAmount), // first, the amounts
Collectors.summingDouble(Itemized::getTax), // second, the sums
Map::entry // and combine them as an Entry
)
);
This should give you a Map.Entry<Double, Double> with the sum of amounts as the key and the sum of tax as the value, which you can extract.
Edit:
Tested and compiled it - it works. Here we go:
ItemizedTest.java
public class ItemizedTest
{
static Itemized[] getCharges()
{
// sums should be first param = 30.6, second param = 75
return new Itemized[] { new Itemized(10, 20), new Itemized(10.4,22), new Itemized(10.2, 33) };
}
public static void main(String[] args)
{
Map.Entry<Double, Double> sums = Arrays.stream(getCharges())
.collect(Collectors
.teeing(
Collectors.summingDouble(Itemized::getAmount),
Collectors.summingDouble(Itemized::getTax),
Map::entry
)
);
System.out.println("sum of amounts: "+sums.getKey());
System.out.println("sum of tax: "+sums.getValue());
}
}
Itemized.java
public final class Itemized
{
final double amount;
final double tax;
public double getAmount()
{
return amount;
}
public double getTax()
{
return tax;
}
public Itemized(double amount, double tax)
{
super();
this.amount = amount;
this.tax = tax;
}
}
Output:
sum of amounts: 30.6
sum of tax: 75.0
P.S.: teeing Collector is only available in Java 12+.
Instead of summing by field, you define a custom object to hold both field's sum values:
ItemizedValues {
private double amount;
private double tax;
public static final ItemizedValues EMPTY = new ItemizedValues(0, 0);
// Constructor - getter - setter
public static ItemizedValues of(Itemized item) {
return new ItemizedValues(amount, tax);
}
public static ItemizedValues sum(ItemizedValues a, ItemizedValues b) {
// Sum the fields respectively
// It's your choice to reuse the existing instances, modify the values or create a brand new one
}
}
Arrays.stream(getCharges())
.map(ItemizedValues::of)
.reduce(ItemizedValues.EMPTY, ItemizedValues::sum);
With a data structure that can allow one to accumulate both sums, you can reduce the stream to a single object.
This is using double[] to hold totalAmount at index 0 and totalTax at index 1 (other options include SimpleEntry, Pair):
double[] res = Arrays.stream(getCharges())
.map(ch -> new double[] { ch.getAmount(), ch.getTax() })
.reduce(new double[] { 0, 0 },
(a1, a2) -> new double[] { a1[0] + a2[0], a1[1] + a2[1] });
double totalAmount = res[0],
totalTax = res[1];
You can do it by using Entry but still you will end up in creating lot of objects, the best solution i would suggest is for loop answered by NimChimpsky
Entry<Double, Double> entry = Arrays.stream(new Itemized[] {i1,i2})
.map(it->new AbstractMap.SimpleEntry<>(it.getAmount(), it.getTax()))
.reduce(new AbstractMap.SimpleEntry<>(0.0,0.0),
(a,b)->new AbstractMap.SimpleEntry<>(a.getKey()+b.getKey(),a.getValue()+b.getValue()));
System.out.println("Amount : "+entry.getKey());
System.out.println("Tax : "+entry.getValue());
In your specific case, it's could be done by using your Itemized class as value holder.
Itemized result = Arrays.stream(getCharges())
.reduce(new Itemized(), (acc, item) -> {
acc.setAmount(acc.getAmount() + item.getAmount());
acc.setTax(acc.getTax() + item.getTax());
return acc;
});
double totalAmount = result.getAmount();
double totalTax = result.getTax();

Summing a List of BigIntegers using streams in Java [duplicate]

I have a collection of BigDecimals (in this example, a LinkedList) that I would like to add together. Is it possible to use streams for this?
I noticed the Stream class has several methods
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
Each of which has a convenient sum() method. But, as we know, float and double arithmetic is almost always a bad idea.
So, is there a convenient way to sum up BigDecimals?
This is the code I have so far.
public static void main(String[] args) {
LinkedList<BigDecimal> values = new LinkedList<>();
values.add(BigDecimal.valueOf(.1));
values.add(BigDecimal.valueOf(1.1));
values.add(BigDecimal.valueOf(2.1));
values.add(BigDecimal.valueOf(.1));
// Classical Java approach
BigDecimal sum = BigDecimal.ZERO;
for(BigDecimal value : values) {
System.out.println(value);
sum = sum.add(value);
}
System.out.println("Sum = " + sum);
// Java 8 approach
values.forEach((value) -> System.out.println(value));
System.out.println("Sum = " + values.stream().mapToDouble(BigDecimal::doubleValue).sum());
System.out.println(values.stream().mapToDouble(BigDecimal::doubleValue).summaryStatistics().toString());
}
As you can see, I am summing up the BigDecimals using BigDecimal::doubleValue(), but this is (as expected) not precise.
Post-answer edit for posterity:
Both answers were extremely helpful. I wanted to add a little: my real-life scenario does not involve a collection of raw BigDecimals, they are wrapped in an invoice. But, I was able to modify Aman Agnihotri's answer to account for this by using the map() function for stream:
public static void main(String[] args) {
LinkedList<Invoice> invoices = new LinkedList<>();
invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));
// Classical Java approach
BigDecimal sum = BigDecimal.ZERO;
for(Invoice invoice : invoices) {
BigDecimal total = invoice.unit_price.multiply(invoice.quantity);
System.out.println(total);
sum = sum.add(total);
}
System.out.println("Sum = " + sum);
// Java 8 approach
invoices.forEach((invoice) -> System.out.println(invoice.total()));
System.out.println("Sum = " + invoices.stream().map((x) -> x.total()).reduce((x, y) -> x.add(y)).get());
}
static class Invoice {
String company;
String invoice_number;
BigDecimal unit_price;
BigDecimal quantity;
public Invoice() {
unit_price = BigDecimal.ZERO;
quantity = BigDecimal.ZERO;
}
public Invoice(String company, String invoice_number, BigDecimal unit_price, BigDecimal quantity) {
this.company = company;
this.invoice_number = invoice_number;
this.unit_price = unit_price;
this.quantity = quantity;
}
public BigDecimal total() {
return unit_price.multiply(quantity);
}
public void setUnit_price(BigDecimal unit_price) {
this.unit_price = unit_price;
}
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public void setInvoice_number(String invoice_number) {
this.invoice_number = invoice_number;
}
public void setCompany(String company) {
this.company = company;
}
public BigDecimal getUnit_price() {
return unit_price;
}
public BigDecimal getQuantity() {
return quantity;
}
public String getInvoice_number() {
return invoice_number;
}
public String getCompany() {
return company;
}
}
Original answer
Yes, this is possible:
List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
What it does is:
Obtain a List<BigDecimal>.
Turn it into a Stream<BigDecimal>
Call the reduce method.
3.1. We supply an identity value for addition, namely BigDecimal.ZERO.
3.2. We specify the BinaryOperator<BigDecimal>, which adds two BigDecimal's, via a method reference BigDecimal::add.
Updated answer, after edit
I see that you have added new data, therefore the new answer will become:
List<Invoice> invoiceList = new ArrayList<>();
//populate
Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
BigDecimal result = invoiceList.stream()
.map(totalMapper)
.reduce(BigDecimal.ZERO, BigDecimal::add);
It is mostly the same, except that I have added a totalMapper variable, that has a function from Invoice to BigDecimal and returns the total price of that invoice.
Then I obtain a Stream<Invoice>, map it to a Stream<BigDecimal> and then reduce it to a BigDecimal.
Now, from an OOP design point I would advice you to also actually use the total() method, which you have already defined, then it even becomes easier:
List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Here we directly use the method reference in the map method.
This post already has a checked answer, but the answer doesn't filter for null values. The correct answer should prevent null values by using the Object::nonNull function as a predicate.
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.filter(Objects::nonNull)
.filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
.reduce(BigDecimal.ZERO, BigDecimal::add);
This prevents null values from attempting to be summed as we reduce.
You can sum up the values of a BigDecimal stream using a reusable Collector named summingUp:
BigDecimal sum = bigDecimalStream.collect(summingUp());
The Collector can be implemented like this:
public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
Use this approach to sum the list of BigDecimal:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();
This approach maps each BigDecimal as a BigDecimal only and reduces them by summing them, which is then returned using the get() method.
Here's another simple way to do the same summing:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();
Update
If I were to write the class and lambda expression in the edited question, I would have written it as follows:
import java.math.BigDecimal;
import java.util.LinkedList;
public class Demo
{
public static void main(String[] args)
{
LinkedList<Invoice> invoices = new LinkedList<>();
invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));
// Java 8 approach, using Method Reference for mapping purposes.
invoices.stream().map(Invoice::total).forEach(System.out::println);
System.out.println("Sum = " + invoices.stream().map(Invoice::total).reduce((x, y) -> x.add(y)).get());
}
// This is just my style of writing classes. Yours can differ.
static class Invoice
{
private String company;
private String number;
private BigDecimal unitPrice;
private BigDecimal quantity;
public Invoice()
{
unitPrice = quantity = BigDecimal.ZERO;
}
public Invoice(String company, String number, BigDecimal unitPrice, BigDecimal quantity)
{
setCompany(company);
setNumber(number);
setUnitPrice(unitPrice);
setQuantity(quantity);
}
public BigDecimal total()
{
return unitPrice.multiply(quantity);
}
public String getCompany()
{
return company;
}
public void setCompany(String company)
{
this.company = company;
}
public String getNumber()
{
return number;
}
public void setNumber(String number)
{
this.number = number;
}
public BigDecimal getUnitPrice()
{
return unitPrice;
}
public void setUnitPrice(BigDecimal unitPrice)
{
this.unitPrice = unitPrice;
}
public BigDecimal getQuantity()
{
return quantity;
}
public void setQuantity(BigDecimal quantity)
{
this.quantity = quantity;
}
}
}
If you don't mind a third party dependency, there is a class named Collectors2 in Eclipse Collections which contains methods returning Collectors for summing and summarizing BigDecimal and BigInteger. These methods take a Function as a parameter so you can extract a BigDecimal or BigInteger value from an object.
List<BigDecimal> list = mList(
BigDecimal.valueOf(0.1),
BigDecimal.valueOf(1.1),
BigDecimal.valueOf(2.1),
BigDecimal.valueOf(0.1));
BigDecimal sum =
list.stream().collect(Collectors2.summingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), sum);
BigDecimalSummaryStatistics statistics =
list.stream().collect(Collectors2.summarizingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), statistics.getSum());
Assert.assertEquals(BigDecimal.valueOf(0.1), statistics.getMin());
Assert.assertEquals(BigDecimal.valueOf(2.1), statistics.getMax());
Assert.assertEquals(BigDecimal.valueOf(0.85), statistics.getAverage());
Note: I am a committer for Eclipse Collections.

Summing BigDecimal with Collectors [duplicate]

I have a collection of BigDecimals (in this example, a LinkedList) that I would like to add together. Is it possible to use streams for this?
I noticed the Stream class has several methods
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
Each of which has a convenient sum() method. But, as we know, float and double arithmetic is almost always a bad idea.
So, is there a convenient way to sum up BigDecimals?
This is the code I have so far.
public static void main(String[] args) {
LinkedList<BigDecimal> values = new LinkedList<>();
values.add(BigDecimal.valueOf(.1));
values.add(BigDecimal.valueOf(1.1));
values.add(BigDecimal.valueOf(2.1));
values.add(BigDecimal.valueOf(.1));
// Classical Java approach
BigDecimal sum = BigDecimal.ZERO;
for(BigDecimal value : values) {
System.out.println(value);
sum = sum.add(value);
}
System.out.println("Sum = " + sum);
// Java 8 approach
values.forEach((value) -> System.out.println(value));
System.out.println("Sum = " + values.stream().mapToDouble(BigDecimal::doubleValue).sum());
System.out.println(values.stream().mapToDouble(BigDecimal::doubleValue).summaryStatistics().toString());
}
As you can see, I am summing up the BigDecimals using BigDecimal::doubleValue(), but this is (as expected) not precise.
Post-answer edit for posterity:
Both answers were extremely helpful. I wanted to add a little: my real-life scenario does not involve a collection of raw BigDecimals, they are wrapped in an invoice. But, I was able to modify Aman Agnihotri's answer to account for this by using the map() function for stream:
public static void main(String[] args) {
LinkedList<Invoice> invoices = new LinkedList<>();
invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));
// Classical Java approach
BigDecimal sum = BigDecimal.ZERO;
for(Invoice invoice : invoices) {
BigDecimal total = invoice.unit_price.multiply(invoice.quantity);
System.out.println(total);
sum = sum.add(total);
}
System.out.println("Sum = " + sum);
// Java 8 approach
invoices.forEach((invoice) -> System.out.println(invoice.total()));
System.out.println("Sum = " + invoices.stream().map((x) -> x.total()).reduce((x, y) -> x.add(y)).get());
}
static class Invoice {
String company;
String invoice_number;
BigDecimal unit_price;
BigDecimal quantity;
public Invoice() {
unit_price = BigDecimal.ZERO;
quantity = BigDecimal.ZERO;
}
public Invoice(String company, String invoice_number, BigDecimal unit_price, BigDecimal quantity) {
this.company = company;
this.invoice_number = invoice_number;
this.unit_price = unit_price;
this.quantity = quantity;
}
public BigDecimal total() {
return unit_price.multiply(quantity);
}
public void setUnit_price(BigDecimal unit_price) {
this.unit_price = unit_price;
}
public void setQuantity(BigDecimal quantity) {
this.quantity = quantity;
}
public void setInvoice_number(String invoice_number) {
this.invoice_number = invoice_number;
}
public void setCompany(String company) {
this.company = company;
}
public BigDecimal getUnit_price() {
return unit_price;
}
public BigDecimal getQuantity() {
return quantity;
}
public String getInvoice_number() {
return invoice_number;
}
public String getCompany() {
return company;
}
}
Original answer
Yes, this is possible:
List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
What it does is:
Obtain a List<BigDecimal>.
Turn it into a Stream<BigDecimal>
Call the reduce method.
3.1. We supply an identity value for addition, namely BigDecimal.ZERO.
3.2. We specify the BinaryOperator<BigDecimal>, which adds two BigDecimal's, via a method reference BigDecimal::add.
Updated answer, after edit
I see that you have added new data, therefore the new answer will become:
List<Invoice> invoiceList = new ArrayList<>();
//populate
Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
BigDecimal result = invoiceList.stream()
.map(totalMapper)
.reduce(BigDecimal.ZERO, BigDecimal::add);
It is mostly the same, except that I have added a totalMapper variable, that has a function from Invoice to BigDecimal and returns the total price of that invoice.
Then I obtain a Stream<Invoice>, map it to a Stream<BigDecimal> and then reduce it to a BigDecimal.
Now, from an OOP design point I would advice you to also actually use the total() method, which you have already defined, then it even becomes easier:
List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Here we directly use the method reference in the map method.
This post already has a checked answer, but the answer doesn't filter for null values. The correct answer should prevent null values by using the Object::nonNull function as a predicate.
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.filter(Objects::nonNull)
.filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
.reduce(BigDecimal.ZERO, BigDecimal::add);
This prevents null values from attempting to be summed as we reduce.
You can sum up the values of a BigDecimal stream using a reusable Collector named summingUp:
BigDecimal sum = bigDecimalStream.collect(summingUp());
The Collector can be implemented like this:
public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
Use this approach to sum the list of BigDecimal:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();
This approach maps each BigDecimal as a BigDecimal only and reduces them by summing them, which is then returned using the get() method.
Here's another simple way to do the same summing:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();
Update
If I were to write the class and lambda expression in the edited question, I would have written it as follows:
import java.math.BigDecimal;
import java.util.LinkedList;
public class Demo
{
public static void main(String[] args)
{
LinkedList<Invoice> invoices = new LinkedList<>();
invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));
// Java 8 approach, using Method Reference for mapping purposes.
invoices.stream().map(Invoice::total).forEach(System.out::println);
System.out.println("Sum = " + invoices.stream().map(Invoice::total).reduce((x, y) -> x.add(y)).get());
}
// This is just my style of writing classes. Yours can differ.
static class Invoice
{
private String company;
private String number;
private BigDecimal unitPrice;
private BigDecimal quantity;
public Invoice()
{
unitPrice = quantity = BigDecimal.ZERO;
}
public Invoice(String company, String number, BigDecimal unitPrice, BigDecimal quantity)
{
setCompany(company);
setNumber(number);
setUnitPrice(unitPrice);
setQuantity(quantity);
}
public BigDecimal total()
{
return unitPrice.multiply(quantity);
}
public String getCompany()
{
return company;
}
public void setCompany(String company)
{
this.company = company;
}
public String getNumber()
{
return number;
}
public void setNumber(String number)
{
this.number = number;
}
public BigDecimal getUnitPrice()
{
return unitPrice;
}
public void setUnitPrice(BigDecimal unitPrice)
{
this.unitPrice = unitPrice;
}
public BigDecimal getQuantity()
{
return quantity;
}
public void setQuantity(BigDecimal quantity)
{
this.quantity = quantity;
}
}
}
If you don't mind a third party dependency, there is a class named Collectors2 in Eclipse Collections which contains methods returning Collectors for summing and summarizing BigDecimal and BigInteger. These methods take a Function as a parameter so you can extract a BigDecimal or BigInteger value from an object.
List<BigDecimal> list = mList(
BigDecimal.valueOf(0.1),
BigDecimal.valueOf(1.1),
BigDecimal.valueOf(2.1),
BigDecimal.valueOf(0.1));
BigDecimal sum =
list.stream().collect(Collectors2.summingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), sum);
BigDecimalSummaryStatistics statistics =
list.stream().collect(Collectors2.summarizingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), statistics.getSum());
Assert.assertEquals(BigDecimal.valueOf(0.1), statistics.getMin());
Assert.assertEquals(BigDecimal.valueOf(2.1), statistics.getMax());
Assert.assertEquals(BigDecimal.valueOf(0.85), statistics.getAverage());
Note: I am a committer for Eclipse Collections.

Categories

Resources