Agregando BigDecimales usando Streams

Tengo una colección de BigDecimals (en este ejemplo, una LinkedList ) que me gustaría agregar juntos. ¿Es posible usar transmisiones para esto?

Noté que la clase Stream tiene varios métodos

 Stream::mapToInt Stream::mapToDouble Stream::mapToLong 

Cada uno de los cuales tiene un método conveniente de sum() . Pero, como sabemos, la aritmética float y double casi siempre es una mala idea.

Entonces, ¿hay una manera conveniente de resumir BigDecimales?

Este es el código que tengo hasta ahora.

 public static void main(String[] args) { LinkedList 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()); } 

Como puede ver, estoy resumiendo BigDecimales usando BigDecimal::doubleValue() , pero esto es (como se esperaba) no es preciso.

Edición posterior a la respuesta para la posteridad:

Ambas respuestas fueron extremadamente útiles. Quería agregar un poco: mi escenario de la vida real no involucra una colección de BigDecimal bruto, están envueltos en una factura. Pero, pude modificar la respuesta de Aman Agnihotri para dar cuenta de esto al usar la función map() para transmisión:

 public static void main(String[] args) { LinkedList 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; } } 

Respuesta original

Sí, esto es posible:

 List bdList = new ArrayList<>(); //populate list BigDecimal result = bdList.stream() .reduce(BigDecimal.ZERO, BigDecimal::add); 

Lo que hace es:

  1. Obtenga una List .
  2. Convertirlo en un Stream
  3. Llame al método de reducción.

    3.1. Proporcionamos un valor de identidad para la sum, a saber, BigDecimal.ZERO .

    3.2. Especificamos BinaryOperator , que agrega dos BigDecimal , a través de un método de referencia BigDecimal::add .

Respuesta actualizada, después de editar

Veo que ha agregado datos nuevos, por lo tanto, la nueva respuesta se convertirá en:

 List invoiceList = new ArrayList<>(); //populate Function totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity()); BigDecimal result = invoiceList.stream() .map(totalMapper) .reduce(BigDecimal.ZERO, BigDecimal::add); 

Es casi lo mismo, excepto que he agregado una variable totalMapper , que tiene una función de Invoice a BigDecimal y devuelve el precio total de esa factura.

Luego Stream un Stream , lo mapeo a un Stream y luego lo reduzco a un BigDecimal .

Ahora, desde un punto de diseño de OOP, le aconsejo que también use realmente el método total() , que ya ha definido, y luego se vuelve más fácil:

 List invoiceList = new ArrayList<>(); //populate BigDecimal result = invoiceList.stream() .map(Invoice::total) .reduce(BigDecimal.ZERO, BigDecimal::add); 

Aquí usamos directamente la referencia del método en el método del map .

Utilice este enfoque para sumr la lista de BigDecimal:

 List values = ... // List of BigDecimal objects BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get(); 

Este enfoque asigna cada BigDecimal solo como un BigDecimal y los reduce al sumrlos, que luego se devuelve utilizando el método get() .

Aquí hay otra manera simple de hacer la misma sum:

 List values = ... // List of BigDecimal objects BigDecimal sum = values.stream().reduce(BigDecimal::add).get(); 

Actualizar

Si tuviera que escribir la clase y la expresión lambda en la pregunta editada, la habría escrito de la siguiente manera:

 import java.math.BigDecimal; import java.util.LinkedList; public class Demo { public static void main(String[] args) { LinkedList 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; } } } 

Puede resumir los valores de una secuencia de BigDecimal utilizando un recostackdor reutilizable llamado summingUp :

 BigDecimal sum = bigDecimalStream.collect(summingUp()); 

The Collector se puede implementar así:

 public static Collector summingUp() { return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add); } 

Si no le importa la dependencia de un tercero, hay una clase llamada Collectors2 en las colecciones de Eclipse que contiene métodos que devuelven los recostackdores para sumr y resumir BigDecimal y BigInteger. Estos métodos toman una función como parámetro para que pueda extraer un valor BigDecimal o BigInteger de un objeto.

 List 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()); 

Nota: soy un committer para las colecciones de Eclipse.

Esta publicación ya tiene una respuesta comprobada, pero la respuesta no filtra los valores nulos. La respuesta correcta debería evitar los valores nulos utilizando la función Object :: nonNull como predicado.

 BigDecimal result = invoiceList.stream() .map(Invoice::total) .filter(Objects::nonNull) .filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null)) .reduce(BigDecimal.ZERO, BigDecimal::add); 

Esto evita que los valores nulos intenten sumrse a medida que reducimos.

Se me ocurrió la siguiente implementación de Collector para resolver este problema. Está escrito en Groovy, por lo que puede que tengas que adaptarlo si solo usas Java, pero tiene la ventaja de admitir una secuencia de tipos arbitrarios, siempre que esos tipos sean compatibles con el ctor de BigDecimal:

 public static  Collector summingBigDecimal() { new java.util.stream.Collectors.CollectorImpl( { [BigDecimal.ZERO].toArray(new BigDecimal[1]) }, { BigDecimal[] a, Object t -> a[0] = (t instanceof BigDecimal ? a[0].add(t) : a[0].add(new BigDecimal(t))) }, { BigDecimal[] a, BigDecimal[] b -> a[0].add(b[0]) }, { BigDecimal[] a -> a[0] }, Collections.emptySet()); } 

Estoy seguro de que podría limpiarse un poco, pero ser capaz de hacer cosas como:

 Stream.of("1", 3L, new BigDecimal("5")).collect(Collectors.summingBigDecimal()) 

… han demostrado ser útiles en ciertas situaciones cuando no quiero que me molesten con tener que hacer la conversión de tipo yo mismo.