ストリームを使用してBigDecimalを合計する


178

LinkedList一緒に追加したいBigDecimals(この例では)のコレクションがあります。これにストリームを使用することは可能ですか?

Streamクラスにいくつかのメソッドがあることに気づきました

Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong

それぞれに便利なsum()方法があります。しかし、私たちが知っているようにfloatdouble算術はほとんど常に悪い考えです。

それでは、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());
}

ご覧のとおり、私はBigDecimalをを使用して合計してBigDecimal::doubleValue()いますが、これは(予想どおり)正確ではありません。

後世のための回答後の編集:

どちらの回答も非常に役に立ちました。私は少し追加したかったのですが、私の実際のシナリオにはraw BigDecimalのコレクションは含まれていません。請求書に包まれています。しかし、map()ストリームの関数を使用することで、これを説明するためにAman Agnihotriの回答を変更することができました。

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;
    }
}

回答:


353

元の答え

はい、これは可能です:

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。つまり、追加用のID値を提供します。BigDecimal.ZEROます。

    3.2。メソッド参照を介して、BinaryOperator<BigDecimal>2を追加するを指定します。BigDecimalBigDecimal::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変数from Invoiceを追加したことを除いて、ほとんど同じですが、fromからtoまでの関数を持ち、BigDecimalその請求書の合計金額を返します。

次にを取得しStream<Invoice>、それをにマップしてからにStream<BigDecimal>減らしBigDecimalます。

ここで、OOP設計の点から、total()すでに定義したメソッドも実際に使用することをお勧めします。そうすると、さらに簡単になります。

List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
        .map(Invoice::total)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

ここでは、メソッドでメソッド参照を直接使用しmapます。


12
以下のための1 Invoice::totalinvoice -> invoice.total()
ryvantage 2014年

12
メソッド参照とストリーム操作の間に改行を追加するための+1。どちらもIMHOによって読みやすさが大幅に向上します。
スチュアートマークス

追加したい場合、どのように機能しますか。Invoice:: totalおよびInvoice :: taxを新しい配列に追加しましょう
Richard Lau

Java標準ライブラリにはCollectors.summingInt()、のような整数/倍数を合計する関数がすでにありますが、BigDecimalsの場合はそれらがありません。代わりに書いてreduce(blah blah blah)、ために不足しているコレクタを書く方が良いでしょう読みにくいですBigDecimalし、持っている.collect(summingBigDecimal())あなたのパイプラインの終わりに。
csharpfolk 2017年

2
このアプローチはNullponterExceptionを引き起こす可能性があります
gstackoverflow

11

この投稿にはすでにチェック済みの回答がありますが、回答はnull値をフィルターしません。正解は、Object :: nonNull関数を述語として使用することにより、null値を防止する必要があります。

BigDecimal result = invoiceList.stream()
    .map(Invoice::total)
    .filter(Objects::nonNull)
    .filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
    .reduce(BigDecimal.ZERO, BigDecimal::add);

これにより、削減時にnull値が合計されるのを防ぎます。


7

次の名前の再利用可能なコレクタBigDecimalを使用して、ストリームの値を合計できます。 summingUp

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

Collectorこのように実装することができます。

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

5

このアプローチを使用して、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;
    }
  }
}

.map(n -> n)そこで無駄じゃないの?またget()、必要ありません。
Rohit Jain 14年

@RohitJain:更新されました。ありがとう。呼び出しで返されるのget()値を返すので使用しました。で作業したい場合、または単に合計を印刷したい場合は、うん、必要ありません。しかし、オプションを直接印刷すると、ユーザーが必要とするのではないかと思う構文がベースで直接印刷されます。したがって、から値を取得するために必要です。OptionalreduceOptionalget()Optional[<Value>]get()Optional
Aman Agnihotri 2014年

@ryvantage:はい、あなたのアプローチはまさに私がそれをしたであろう方法です。:)
Aman Agnihotri 14年

無条件のget呼び出しを使用しないでください!場合はvalues空のリストがあるオプションは値を含まないだろうとスローされますNoSuchElementExceptionときgetに呼び出されます。values.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)代わりに使用できます。
eee

4

サードパーティの依存関係を気にしない場合は、名前のクラスがありCollectors2Eclipseのコレクションのためにコレクターを返すメソッドが含ま合計する要約の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コレクションのコミッターです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.