1. ホーム
  2. java

[解決済み】ストリームを使ったBigDecimalsの足し算について

2022-04-13 13:19:08

質問

BigDecimalsのコレクションを持っています(この例では LinkedList を追加したいのですが。このような場合、ストリームを使用することは可能ですか?

に気がつきました。 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());
}

見ての通り、BigDecimals を使って合計しています。 BigDecimal::doubleValue() しかし、これは(予想通り)正確ではありません。

回答後の編集で後世に残す。

どちらの回答も大変参考になりました。少し付け加えると、私の現実のシナリオでは、生の BigDecimal を、インボイスで包む。しかし、Aman Agnihotri氏の回答を修正し、この点を考慮するために 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;
    }
}

解決方法は?

オリジナルの回答

はい、可能です。

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つ追加します。 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 .

さて、OOP設計の観点から、私は実際に使用することをお勧めします。 total() メソッドを定義しておくと、さらに簡単です。

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

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