java - sumar - Agregando BigDecimales usando Streams
sum bigdecimal java (6)
Respuesta original
Sí, esto es posible:
List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
Lo que hace es:
- Obtenga una
List<BigDecimal>
. - Convertirlo en un
Stream<BigDecimal>
Llame al método de reducción.
3.1. Proporcionamos un valor de identidad para la suma, a saber,
BigDecimal.ZERO
.3.2. Especificamos
BinaryOperator<BigDecimal>
, que agrega dosBigDecimal
, a través de un método de referenciaBigDecimal::add
.
Respuesta actualizada, después de editar
Veo que ha agregado datos nuevos, por lo tanto, la nueva respuesta se convertirá en:
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);
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<BigDecimal>
un Stream<Invoice>
, lo mapeo a un Stream<BigDecimal>
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<Invoice> 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
.
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<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());
}
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<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;
}
}
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 sumarse a medida que reducimos.
Puede resumir los valores de una secuencia de BigDecimal
utilizando un Collector reutilizable llamado summingUp
:
BigDecimal sum = bigDecimalStream.collect(summingUp());
The Collector
se puede implementar así:
public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
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 <T> Collector<T, ?, BigDecimal> summingBigDecimal() {
new java.util.stream.Collectors.CollectorImpl<?, ?, BigDecimal>(
{ [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.
Si no le importa la dependencia de un tercero, hay una clase llamada Collectors2 en las Collectors2 de Eclipse que contiene métodos que devuelven los recopiladores para summing y summarizing BigDecimal y BigInteger. Estos métodos toman una Function como parámetro para que pueda extraer un valor BigDecimal o BigInteger de un objeto.
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());
Nota: soy un committer para las colecciones de Eclipse.
Utilice este enfoque para sumar la lista de BigDecimal:
List<BigDecimal> 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 sumarlos, que luego se devuelve utilizando el método get()
.
Aquí hay otra manera simple de hacer la misma suma:
List<BigDecimal> 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<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;
}
}
}