Добавление BigDecimals с помощью потоков


у меня есть коллекция BigDecimals (в этом примере a 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(), но это (как и ожидалось) не точные.

пост-ответ редактировать для потомков:

оба ответа были очень полезны. Я хотел добавить немного: мой реальный сценарий не включает в себя коллекцию raw BigDecimalS, они завернуты в счет-фактуру. Но я смог изменить ответ Амана Агнихотри, чтобы объяснить это, используя

6 112

6 ответов:

оригинальный ответ

Да, это возможно:

List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
        .reduce(BigDecimal.ZERO, BigDecimal::add);

что он делает:

  1. получить List<BigDecimal>.
  2. превратить его в Stream<BigDecimal>
  3. вызовите метод reduce.

    3.1. Мы предоставляем значение идентификатора для добавления, а именно BigDecimal.ZERO.

    3.2. Мы указываем BinaryOperator<BigDecimal>, который добавляет два BigDecimal ' s, Через ссылку на метод 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 метод.

используйте этот подход для суммирования списка 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;
    }
  }
}

вы можете суммировать значения a BigDecimal поток с помощью многоразовыеколлектор имени summingUp:

BigDecimal sum = bigDecimalStream.collect(summingUp());

The Collector может быть реализовано следующим образом:

public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
    return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}

Если вы не возражаете против зависимости от третьей стороны, есть класс с именем Collectors2 на Коллекции Eclipse который содержит методы, возвращающие коллекторы для подведение итогов и подводя итоги 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.

этот пост уже имеет проверенный ответ, но ответ не фильтруется для нулевых значений. Правильный ответ должен предотвратить значения null с помощью функции 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);

это предотвращает попытки суммирования нулевых значений при уменьшении.

Я придумал следующую реализацию коллектора, чтобы решить эту проблему. Он написан на 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())

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