Mam kolekcję BigDecimals (w tym przykładzie a LinkedList
), którą chciałbym dodać. Czy można do tego wykorzystać strumienie?
Zauważyłem, że Stream
klasa ma kilka metod
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
Z których każdy ma wygodną sum()
metodę. Ale, jak wiemy, float
i double
arytmetyka jest prawie zawsze zły pomysł.
Czy jest więc wygodny sposób podsumowania BigDecimals?
To jest kod, który mam do tej pory.
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());
}
Jak widać, podsumowuję wartości BigDecimals przy użyciu BigDecimal::doubleValue()
, ale jest to (zgodnie z oczekiwaniami) nieprecyzyjne.
Edycja po odpowiedzi dla potomnych:
Obie odpowiedzi były niezwykle pomocne. Chciałem trochę dodać: mój scenariusz z życia nie zakłada zbierania surowych BigDecimal
s, są one zawinięte w fakturę. Ale udało mi się zmodyfikować odpowiedź Amana Agnihotri, aby to wyjaśnić, używając map()
funkcji 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;
}
}
źródło
Invoice::total
żywoinvoice -> invoice.total()
.Collectors.summingInt()
, ale pomija je przezBigDecimal
s. Zamiast pisaćreduce(blah blah blah)
to, co jest trudne do odczytania, lepiej byłoby napisać brakujący kolektor dlaBigDecimal
i mieć.collect(summingBigDecimal())
na końcu potoku.Ten post ma już zaznaczoną odpowiedź, ale odpowiedź nie filtruje wartości null. Prawidłowa odpowiedź powinna zapobiec wartościom null, używając funkcji Object :: nonNull jako predykatu.
Zapobiega to próbom sumowania wartości null podczas zmniejszania.
źródło
Możesz podsumować wartości
BigDecimal
strumienia za pomocą kolektora wielokrotnego użytku o nazwie :summingUp
Collector
Mogą być realizowane w ten sposób:źródło
Użyj tego podejścia, aby zsumować listę BigDecimal:
To podejście mapuje każdy BigDecimal tylko jako BigDecimal i redukuje je, sumując je, a następnie zwracane przy użyciu
get()
metody.Oto inny prosty sposób na wykonanie tego samego podsumowania:
Aktualizacja
Gdybym miał w edytowanym pytaniu zapisać klasę i wyrażenie lambda, napisałbym to następująco:
źródło
.map(n -> n)
tam nie jest bezużyteczne? Nieget()
jest też potrzebne.get()
ponieważ zwraca wartość,Optional
która jest zwracana przezreduce
wywołanie. Jeśli ktoś chce pracować zOptional
lub po prostu wydrukować sumę, to tak,get()
nie jest potrzebny. Ale drukowanie Opcjonalne bezpośrednio drukujeOptional[<Value>]
składnię opartą na podstawie której wątpię, by użytkownik potrzebował.get()
Jest więc potrzebny w celu uzyskania wartości zOptional
.get
połączenia! Jeślivalues
jest pustą listą, opcja opcjonalna nie będzie zawierała żadnej wartości i spowoduje wywołanieNoSuchElementException
whenget
. Możesz użyćvalues.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)
zamiast tego.Jeśli nie przeszkadza zależność osoby trzecie, istnieje klasa o nazwie Collectors2 w Eclipse Kolekcje który zawiera metody powrocie Kolektory dla zsumowanie i podsumowując BigDecimal i BigInteger. Te metody przyjmują Function jako parametr, dzięki czemu można wyodrębnić wartość BigDecimal lub BigInteger z obiektu.
Uwaga: jestem promotorem Eclipse Collections.
źródło