Сложение BigDecimals через Java Streams: точность и ограничения

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для суммирования значений BigDecimal с помощью Stream API удобно использовать метод reduce():

Java
Скопировать код
BigDecimal sum = bigDecimals.stream()
                            .reduce(BigDecimal.ZERO, BigDecimal::add);

Переменная bigDecimals содержит вашу коллекцию объектов типа BigDecimal. Метод stream() преобразует ее в поток, а reduce() выполняет непосредственное суммирование, принимая за начальное значение BigDecimal.ZERO и используя метод BigDecimal::add для накопления результата.

Кинга Идем в IT: пошаговый план для смены профессии

Операционные сценарии

Если вам приходилось выполнять сложные вычисления или работать с сложными объектами, вы оцените простоту решения, предложенного Java. это сочетание методов map и reduce. Например, когда перед вами стоит задача рассчитать общую сумму счетов-фактур:

Java
Скопировать код
// Метод подсчета итоговой суммы счетов-фактур
BigDecimal invoiceTotalSum = invoices.stream()
                                     .map(Invoice::total)
                                     .reduce(BigDecimal.ZERO, BigDecimal::add);

Данный код конвертирует каждый счет-фактуру в денежную сумму с помощью метода Invoice::total, а затем суммирует эти суммы. Для обхода проблем с null, можно добавить фильтрацию:

Java
Скопировать код
// Фильтрация null-значений перед суммированием
BigDecimal invoiceTotalSum = invoices.stream()
                                     .filter(Objects::nonNull)
                                     .map(Invoice::total)
                                     .reduce(BigDecimal.ZERO, BigDecimal::add);

Применение метода filter(Objects::nonNull) исключает null значения из потока перед остальными действиями. Это поможет избежать исключения NullPointerException.

Обеспечение точности

Для анализа эффективности кода очень полезным оказываются статистические данные. Eclipse Collections предлагает функцию summarizingBigDecimal():

Java
Скопировать код
// Статистические данные по работе с BigDecimal
MutableBigDecimalSummaryStatistics stats = 
    bigDecimals.stream()
               .collect(Collectors2.summarizingBigDecimal(Function.identity()));

MutableBigDecimalSummaryStatistics предоставляет обширный набор статистических данных, включая общую сумму, среднее значение, количество, минимальное и максимальное значения. Важно применять методы с BigDecimal, не приводя этот тип к double или float, чтобы обеспечить высокую точность выполняемых вычислений.

Визуализация

Процесс суммирования значений BigDecimal можно визуализировать, представив дельфинов, переносящих раковины (которые представляют BigDecimal):

🐬(👜🐚) -> 🐬(👜🐚) -> 🐬(👜🐚) -> ... -> 🐋(💼🐚)

Наша цель – сложить все раковины и передать их киту:

Java
Скопировать код
// Суммирование раковин у дельфинов
BigDecimal total = dolphins.stream()
                           .reduce(BigDecimal.ZERO, BigDecimal::add);

На выходе получается кит, который носит сумку с общим числом раковин (💼🐚). Это соответствует сумме BigDecimal.

Настройка параметров

Тщательная настройка поведения при работе со значениями BigDecimal может быть важной для обеспечения точности и производительности. Вам может пригодиться пользовательский Collector:

Java
Скопировать код
// Настраиваем персонализированный Collector
Collector<BigDecimal, ?, BigDecimal> summingUp = 
    Collector.of(
        () -> new BigDecimal[]{BigDecimal.ZERO},
        (a, t) -> a[0] = a[0].add(t),
        (a, b) -> new BigDecimal[]{a[0].add(b[0])},
        a -> a[0], 
        Collector.Characteristics.IDENTITY_FINISH
    );

Пользовательский Collector, получивший название summingUp, создан особенно для обеспечения точности при накоплении значений.

Защита от скрытых ловушек

Также важно обезопасить свои вычисления от ошибок, вызванных null-значениями:

Java
Скопировать код
// Защита от null-значений
Function<Invoice, BigDecimal> safeInvoiceTotal = invoice -> 
    (invoice != null && invoice.getUnitPrice() != null && invoice.getQuantity() != null)
    ? invoice.total() 
    : BigDecimal.ZERO;

BigDecimal safeSum = invoices.stream()
                             .map(safeInvoiceTotal)
                             .reduce(BigDecimal.ZERO, BigDecimal::add);

Функция safeInvoiceTotal обеспечивает надежную защиту от проблем, связанных с нелевыми значениями, в вашем коде.

Полезные материалы

  1. BigDecimal (Java Platform SE 8) — детальная документация BigDecimal.
  2. Java 8 Stream Tutorial – winterbe — обучающий материал по работе с Streams в Java 8.
  3. Reduction (The Java™ Tutorials) — подробно о сокращающих операциях в Java Streams.
  4. Java Practices->Representing money — описание и особенности работы с денежными суммами в BigDecimal.
  5. c# – is there anyway this LINQ could end up doing too much work? — обсуждение эффективности операций в потоках, например, с BigDecimal.