Добавление BigDecimals с помощью потоков
У меня есть коллекция BigDecimals (в этом примере LinkedList
) что я хотел бы добавить вместе. Возможно ли использовать потоки для этого?
Я заметил Stream
класс имеет несколько методов
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
Каждый из которых имеет удобный sum()
метод. Но, как мы знаем, float
а также double
арифметика почти всегда плохая идея.
Итак, есть ли удобный способ суммировать BigDecimals?
Это код, который я до сих пор.
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());
}
Как видите, я суммирую BigDecimals используя BigDecimal::doubleValue()
, но это (как и ожидалось) не точно.
Редактирование пост-ответа для потомков:
Оба ответа были чрезвычайно полезны. Я хотел бы добавить немного: мой реальный сценарий не включает в себя коллекцию необработанных BigDecimal
с, они завернуты в счет-фактуру. Но я смог изменить ответ Амана Агнихотри, чтобы объяснить это, используя map()
функция для потока:
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;
}
}
5 ответов
Оригинальный ответ
Да, это возможно
List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
Что это делает:
- Получить
List<BigDecimal>
, - Преврати это в
Stream<BigDecimal>
Вызовите метод сокращения.
3.1. Мы предоставляем значение личности для добавления, а именно
BigDecimal.ZERO
,3.2. Мы указываем
BinaryOperator<BigDecimal>
, который добавляет дваBigDecimal
с помощью метода ссылкиBigDecimal::add
,
Обновленный ответ, после редактирования
Я вижу, что вы добавили новые данные, поэтому новый ответ станет:
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);
Это в основном то же самое, за исключением того, что я добавил totalMapper
переменная, которая имеет функцию из Invoice
в BigDecimal
и возвращает общую стоимость этого счета.
Тогда я получаю Stream<Invoice>
, сопоставьте это с Stream<BigDecimal>
а затем уменьшить его до BigDecimal
,
Теперь, с точки зрения ООП, я бы посоветовал вам также использовать total()
Метод, который вы уже определили, тогда даже станет проще:
List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Здесь мы напрямую используем ссылку на метод в map
метод.
В этом сообщении уже есть проверенный ответ, но в ответе нет фильтра для нулевых значений. Правильный ответ должен предотвращать нулевые значения, используя функцию Object::nonNull в качестве предиката.
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.filter(Objects::nonNull)
.filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
.reduce(BigDecimal.ZERO, BigDecimal::add);
Это предотвращает попытку суммирования нулевых значений при уменьшении.
Вы можете суммировать значения BigDecimal
поток с использованием многоразового коллектора с именем summingUp
:
BigDecimal sum = bigDecimalStream.collect(summingUp());
Collector
может быть реализовано так:
public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
Используйте этот подход для суммирования списка BigDecimal:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();
Этот подход отображает каждый BigDecimal только как BigDecimal и уменьшает их путем суммирования, которое затем возвращается с использованием get()
метод.
Вот еще один простой способ сделать то же суммирование:
List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();
Обновить
Если бы я написал класс и лямбда-выражение в редактируемом вопросе, я написал бы его следующим образом:
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;
}
}
}
Если вы не возражаете против сторонней зависимости, в Eclipse Collections есть класс Collectors2, который содержит методы, возвращающие Collectors для суммирования и суммирования BigDecimal и BigInteger. Эти методы принимают функцию в качестве параметра, поэтому вы можете извлечь значение BigDecimal или BigInteger из объекта.
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());
Примечание: я являюсь коммиттером для Eclipse Collections.
Я придумал следующую реализацию Collector для решения этой проблемы. Он написан на Groovy, поэтому вам, возможно, придется адаптировать его, если вы используете только Java, но у него есть преимущество поддержки потока произвольных типов при условии, что эти типы поддерживаются ctor 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());
}
Я уверен, что это можно немного почистить, но я могу делать такие вещи, как:
Stream.of("1", 3L, new BigDecimal("5")).collect(Collectors.summingBigDecimal())
... оказался полезным в определенных ситуациях, когда я не хочу беспокоиться о необходимости выполнять преобразование типов самостоятельно.