使用 Streams 将 BigDecimals 相加

Adding up BigDecimals using Streams

提问人:ryvantage 提问时间:3/25/2014 最后编辑:Basil Bourqueryvantage 更新时间:4/21/2023 访问量:204373

问:

我有一个 BigDecimals 的集合(在本例中为 a ),我想将它们加在一起。是否可以使用流来计算它们的总和?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()

为后代提供的答案后编辑:

这两个答案都非常有帮助。我想补充一点:我的现实生活中的场景不涉及原始 s 的集合,它们被包装在发票中。但是,我能够修改 Aman Agnihotri 的答案,通过使用 stream 函数来解释这一点:BigDecimalmap()

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;
    }
}
java bigdecimal java-8 java-stream

评论

1赞 Lluis Martinez 7/2/2022
经典的 Java 方法是最好的方法。

答:

443赞 skiwi 3/25/2014 #1

原始答案

是的,这是可能的:

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. 我们通过方法引用指定 ,它加了两个 's。BinaryOperator<BigDecimal>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);

它基本上是一样的,只是我添加了一个变量,该变量具有从 to 的函数并返回该发票的总价。totalMapperInvoiceBigDecimal

然后我得到一个 ,将其映射到 a,然后将其简化为 .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

评论

16赞 ryvantage 3/26/2014
+1 表示 vs .Invoice::totalinvoice -> invoice.total()
13赞 Stuart Marks 3/26/2014
+1 用于方法引用和在流操作之间添加换行符,恕我直言,这两者都大大提高了可读性。
0赞 Richard Lau 11/13/2015
如果我想将 Invoice::total 和 Invoice::tax 添加到一个新数组中,它会如何工作
0赞 csharpfolk 5/23/2017
Java 标准库已经有用于对整数/双精度求和的函数,例如 ,但在 s 中缺少它们。与其写难以阅读的,不如在管道的末尾写出缺少的收集器,并拥有。Collectors.summingInt()BigDecimalreduce(blah blah blah)BigDecimal.collect(summingBigDecimal())
2赞 gstackoverflow 9/20/2017
此方法可能导致 NullponterException
7赞 Aman Agnihotri 3/25/2014 #2

使用此方法对 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();

更新

如果我在编辑的问题中编写 class 和 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;
    }
  }
}

评论

0赞 Rohit Jain 3/25/2014
那里不是没用吗?也不需要。.map(n -> n)get()
0赞 Aman Agnihotri 3/25/2014
@RohitJain:已更新。谢谢。我使用它,因为它返回调用返回的值。如果一个人想使用或只是打印出总和,那么是的,不需要。但是打印可选直接打印基于语法,我怀疑用户会需要。因此,需要以某种方式从 .get()OptionalreduceOptionalget()Optional[<Value>]get()Optional
0赞 Aman Agnihotri 3/26/2014
@ryvantage:是的,你的方法正是我本来会做的。:)
0赞 eee 4/2/2016
不要使用无条件调用!如果为空列表,则可选将不包含任何值,并将抛出调用 when。您可以改用。getvaluesNoSuchElementExceptiongetvalues.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)
18赞 Igor Akkerman 12/11/2016 #3

定义可重用的Collector

您可以使用名为 BigDecimalsummingUp

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

可以这样实现:Collector

public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
    return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
6赞 Donald Raab 4/28/2018 #4

如果您不介意第三方依赖关系,Eclipse Collections 中有一个名为 Collectors2 的类,其中包含返回 Collectors 的方法,用于对 BigDecimal 和 BigInteger 进行求和和汇总。这些方法将 Function 作为参数,以便您可以从对象中提取 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 Collections 的提交者。

21赞 Siraj 5/29/2018 #5

这篇文章已经有一个经过检查的答案,但答案不会过滤 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 值求和。

评论

2赞 Lluis Martinez 7/2/2022
OP 说“我有一个 BigDecimal 的集合”,null 不是 BigDecimal。