Thêm BigDecimals bằng Luồng


178

Tôi có một bộ sưu tập BigDecimals (trong ví dụ này, a LinkedList) mà tôi muốn thêm vào với nhau. Có thể sử dụng các luồng cho việc này?

Tôi nhận thấy Streamlớp học có một số phương pháp

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

Mỗi trong số đó có một sum()phương pháp thuận tiện . Nhưng, như chúng ta đã biết, floatdoublesố học hầu như luôn là một ý tưởng tồi.

Vì vậy, có cách nào thuận tiện để tổng hợp BigDecimals không?

Đây là mã tôi có cho đến nay.

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

Như bạn có thể thấy, tôi đang tóm tắt BigDecimals bằng cách sử dụng BigDecimal::doubleValue(), nhưng điều này (như mong đợi) không chính xác.

Chỉnh sửa sau câu trả lời cho hậu thế:

Cả hai câu trả lời đều rất hữu ích. Tôi muốn thêm một chút: kịch bản đời thực của tôi không liên quan đến một bộ sưu tập BigDecimals thô , chúng được gói trong một hóa đơn. Nhưng, tôi đã có thể sửa đổi câu trả lời của Aman Agnihotri để giải thích điều này bằng cách sử dụng map()chức năng cho luồng:

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

Câu trả lời:


353

Câu trả lời gốc

Vâng, điều này là có thể:

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

Những gì nó làm là:

  1. Có được a List<BigDecimal>.
  2. Biến nó thành một Stream<BigDecimal>
  3. Gọi phương thức rút gọn.

    3.1. Chúng tôi cung cấp một giá trị nhận dạng để bổ sung, cụ thể là BigDecimal.ZERO.

    3.2. Chúng tôi chỉ định BinaryOperator<BigDecimal>, thêm hai BigDecimal, thông qua tham chiếu phương thức BigDecimal::add.

Cập nhật câu trả lời, sau khi chỉnh sửa

Tôi thấy rằng bạn đã thêm dữ liệu mới, do đó câu trả lời mới sẽ trở thành:

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

Nó hầu như giống nhau, ngoại trừ việc tôi đã thêm một totalMapperbiến, có chức năng từ Invoiceđến BigDecimalvà trả lại tổng giá của hóa đơn đó.

Sau đó, tôi có được một Stream<Invoice>, ánh xạ nó đến a Stream<BigDecimal>và sau đó giảm nó xuống a BigDecimal.

Bây giờ, từ quan điểm thiết kế OOP, tôi sẽ khuyên bạn thực sự sử dụng total()phương thức mà bạn đã xác định, sau đó nó thậm chí còn trở nên dễ dàng hơn:

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

Ở đây chúng tôi trực tiếp sử dụng tham chiếu phương thức trong mapphương thức.


12
+1 cho Invoice::totalso với invoice -> invoice.total().
ry Bổ trợ

12
+1 cho tham chiếu phương thức và để thêm ngắt dòng giữa các hoạt động luồng, cả hai đều IMHO cải thiện đáng kể khả năng đọc.
Stuart Marks

Nó sẽ hoạt động như thế nào nếu tôi muốn thêm hãy nói Invoice :: Total và Invoice :: tax vào một mảng mới
Richard Lau

Thư viện chuẩn Java đã có các hàm để tính tổng các số nguyên / nhân đôi Collectors.summingInt(), nhưng bỏ lỡ chúng cho BigDecimals. Thay vì viết reduce(blah blah blah)khó đọc, tốt hơn hết là viết collector bị thiếu BigDecimal.collect(summingBigDecimal())ở cuối đường ống của bạn.
csharpfolk

2
Cách tiếp cận này có thể dẫn đến NullponterException
gstackoverflow

11

Bài đăng này đã có câu trả lời được kiểm tra, nhưng câu trả lời không lọc các giá trị null. Câu trả lời đúng sẽ ngăn các giá trị null bằng cách sử dụng hàm Object :: nonNull làm vị ngữ.

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

Điều này ngăn các giá trị null cố gắng được tóm tắt khi chúng tôi giảm.


7

Bạn có thể tổng hợp các giá trị của BigDecimalluồng bằng Collector có thể sử dụng lại có tên :summingUp

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

Collectorthể được thực hiện như thế này:

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

5

Sử dụng phương pháp này để tổng hợp danh sách BigDecimal:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();

Cách tiếp cận này ánh xạ mỗi BigDecimal thành một BigDecimal duy nhất và giảm chúng bằng cách tính tổng chúng, sau đó được trả về bằng get()phương thức.

Đây là một cách đơn giản khác để thực hiện việc tóm tắt tương tự:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();

Cập nhật

Nếu tôi viết biểu thức lớp và lambda trong câu hỏi đã chỉnh sửa, tôi sẽ viết nó như sau:

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

Không .map(n -> n)có vô dụng ở đó à? Cũng get()không cần thiết.
Rohit Jain

@RohitJain: Đã cập nhật. Cảm ơn. Tôi đã sử dụng get()vì nó trả về giá trị của cuộc gọi Optionalđược trả về reduce. Nếu một người muốn làm việc với Optionalhoặc chỉ in ra số tiền, thì ừ, get()không cần thiết. Nhưng in Optional[<Value>]cú pháp trực tiếp tùy chọn in mà tôi nghi ngờ người dùng sẽ cần. Vì vậy, get()là cần thiết trong một cách để có được giá trị từ Optional.
Aman Agnihotri

@ryaugeage: Vâng, cách tiếp cận của bạn chính xác là cách tôi sẽ làm nó. :)
Aman Agnihotri

Đừng sử dụng một getcuộc gọi vô điều kiện ! Nếu valueslà một danh sách trống, tùy chọn sẽ không chứa giá trị và sẽ ném NoSuchElementExceptionkhi getđược gọi. Bạn có thể sử dụng values.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)thay thế.
eee

4

Nếu bạn không nhớ một sự phụ thuộc của bên thứ ba, có một lớp có tên Collectors2 trong Eclipse Bộ sưu tập , trong đó có phương pháp trở về Bộ thu tổng hợptóm tắt BigDecimal và BigInteger. Các phương thức này lấy Hàm làm tham số để bạn có thể trích xuất giá trị BigDecimal hoặc BigInteger từ một đối tượng.

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());

Lưu ý: Tôi là người đi làm cho Bộ sưu tập Eclipse.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.