Java 使用 Streams 添加 BigDecimals

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22635945/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 16:58:47  来源:igfitidea点击:

Adding up BigDecimals using Streams

javabigdecimaljava-8java-stream

提问by ryvantage

I have a collection of BigDecimals (in this example, a LinkedList) that I would like to add together. Is it possible to use streams for this?

我有一组 BigDecimals(在本例中为 a LinkedList),我想将它们加在一起。是否可以为此使用流?

I noticed the Streamclass has several methods

我注意到这个Stream类有几个方法

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

Each of which has a convenient sum()method. But, as we know, floatand doublearithmetic is almost always a bad idea.

每个都有一个方便的sum()方法。但是,我们知道,floatdouble算术几乎总是一个坏主意。

So, is there a convenient way to sum up BigDecimals?

那么,有没有一种方便的方法来总结 BigDecimals?

This is the code I have so far.

这是我到目前为止的代码。

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

As you can see, I am summing up the BigDecimals using BigDecimal::doubleValue(), but this is (as expected) not precise.

如您所见,我正在使用 总结 BigDecimals BigDecimal::doubleValue(),但这(如预期)并不精确。

Post-answer edit for posterity:

子孙后代的回答后编辑:

Both answers were extremely helpful. I wanted to add a little: my real-life scenario does not involve a collection of raw BigDecimals, they are wrapped in an invoice. But, I was able to modify Aman Agnihotri's answer to account for this by using the map()function for stream:

这两个答案都非常有帮助。我想补充一点:我的现实场景不涉及原始BigDecimals的集合,它们被包装在发票中。但是,我能够通过使用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;
    }
}

采纳答案by skiwi

Original answer

原答案

Yes, this is possible:

是的,这是可能的:

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

What it does is:

它的作用是:

  1. Obtain a List<BigDecimal>.
  2. Turn it into a Stream<BigDecimal>
  3. Call the reduce method.

    3.1. We supply an identity value for addition, namely BigDecimal.ZERO.

    3.2. We specify the BinaryOperator<BigDecimal>, which adds two BigDecimal's, via a method reference BigDecimal::add.

  1. 获得一个List<BigDecimal>.
  2. 把它变成一个 Stream<BigDecimal>
  3. 调用reduce 方法。

    3.1. 我们为加法提供一个身份值,即BigDecimal.ZERO

    3.2. 我们通过方法引用指定BinaryOperator<BigDecimal>,它添加了两个。BigDecimalBigDecimal::add

Updated answer, after edit

更新后的答案,编辑后

I see that you have added new data, therefore the new answer will become:

我看到您添加了新数据,因此新答案将变为:

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

It is mostly the same, except that I have added a totalMappervariable, that has a function from Invoiceto BigDecimaland returns the total price of that invoice.

除了我添加了一个totalMapper变量,它有一个函数 from InvoicetoBigDecimal并返回该发票的总价,它几乎是相同的。

Then I obtain a Stream<Invoice>, map it to a Stream<BigDecimal>and then reduce it to a BigDecimal.

然后我获得 a Stream<Invoice>,将其映射到 aStream<BigDecimal>然后将其减少到 a BigDecimal

Now, from an OOP design point I would advice you to also actually use the total()method, which you have already defined, then it even becomes easier:

现在,从 OOP 设计的角度来看,我建议您也实际使用total()您已经定义的方法,然后它甚至变得更容易:

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

Here we directly use the method reference in the mapmethod.

这里我们直接使用方法中的map方法引用。

回答by Aman Agnihotri

Use this approach to sum the list of BigDecimal:

使用此方法对 BigDecimal 列表求和:

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

This approach maps each BigDecimal as a BigDecimal only and reduces them by summing them, which is then returned using the get()method.

这种方法仅将每个 BigDecimal 映射为 BigDecimal 并通过对它们求和来减少它们,然后使用该get()方法返回。

Here's another simple way to do the same summing:

这是进行相同求和的另一种简单方法:

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

Update

更新

If I were to write the class and lambda expression in the edited question, I would have written it as follows:

如果我在编辑过的问题中编写类和 lambda 表达式,我会这样写:

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

回答by Igor Akkerman

You can sum up the values of a BigDecimalstream using a reusableCollectornamed summingUp:

您可以BigDecimal使用名为的可重用收集器来总结流的值summingUp

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

The Collectorcan be implemented like this:

Collector可以实现这样的:

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

回答by Donald Raab

If you don't mind a third party dependency, there is a class named Collectors2in Eclipse Collectionswhich contains methods returning Collectors for summingand summarizingBigDecimal and BigInteger. These methods take a Functionas a parameter so you can extract a BigDecimal or BigInteger value from an object.

如果你不介意第三方的依赖,有一个名为类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());

Note: I am a committer for Eclipse Collections.

注意:我是 Eclipse Collections 的提交者。

回答by Siraj

This post already has a checked answer, but the answer doesn't filter for null values. The correct answer should prevent null values by using the Object::nonNull function as a predicate.

这篇文章已经有一个检查过的答案,但答案没有过滤空值。正确答案应该通过使用 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);

This prevents null values from attempting to be summed as we reduce.

这可以防止在我们减少时尝试对空值求和。