У меня есть коллекция 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;
}
}
источник
Invoice::total
противinvoice -> invoice.total()
.Collectors.summingInt()
, но пропускает их поBigDecimal
s. Вместо того, чтобы писатьreduce(blah blah blah)
что-то трудное для чтения, лучше написать отсутствующий коллекторBigDecimal
и иметь его.collect(summingBigDecimal())
в конце вашего конвейера.В этом сообщении уже есть проверенный ответ, но в ответе нет фильтра по пустым значениям. Правильный ответ должен предотвращать нулевые значения, используя функцию Object :: nonNull в качестве предиката.
Это предотвращает попытку суммирования нулевых значений при уменьшении.
источник
Вы можете суммировать значения
BigDecimal
потока, используя многоразовый коллектор с именемsummingUp
:Collector
Может быть реализована следующим образом:источник
Используйте этот подход для суммирования списка BigDecimal:
Этот подход отображает каждый BigDecimal только как BigDecimal и уменьшает их путем суммирования, которое затем возвращается с использованием
get()
метода.Вот еще один простой способ сделать то же суммирование:
Обновить
Если бы я написал класс и лямбда-выражение в редактируемом вопросе, я написал бы его следующим образом:
источник
.map(n -> n)
Там не бесполезно? Такжеget()
не нужно.get()
поскольку он возвращает значение,Optional
которое возвращается приreduce
вызове. Если кто-то хочет работать сOptional
или просто распечатать сумму, то да,get()
не нужно. Но печать Optional напрямую печатаетOptional[<Value>]
синтаксис, который, я сомневаюсь, понадобится пользователю. Такget()
что нужно, чтобы получить значение отOptional
.get
звонок! Еслиvalues
это пустой список, необязательный не будет содержать значения и будет вызывать,NoSuchElementException
когдаget
вызывается. Вы можете использоватьvalues.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)
вместо этого.Если вы не возражаете против сторонней зависимости, существует класс с именем Collectors2 в Eclipse Collections который содержит методы, возвращающие Collectors для суммирования и суммирования BigDecimal и BigInteger. Эти методы принимают функцию в качестве параметра, поэтому вы можете извлечь значение BigDecimal или BigInteger из объекта.
Примечание: я являюсь коммиттером для Eclipse Collections.
источник