首页 文章

使用Streams添加BigDecimals

提问于
浏览
122

我有一个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());
}

正如你所看到的,我正在使用 BigDecimal::doubleValue() 总结BigDecimals,但这(正如预期的那样)并不准确 .

Post-answer edit for posterity:

这两个答案都非常有帮助 . 我想补充一点:我的现实场景不涉及原始 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;
    }
}

5 回答

  • 4

    原始答案

    是的,这是可能的:

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

    它的作用是:

    • 获得 List<BigDecimal> .

    • 把它变成 Stream<BigDecimal>

    • 调用reduce方法 .

    3.1 . 我们提供添加的标识值,即 BigDecimal.ZERO .

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

    编辑后更新了答案

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

    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 变量,其函数从 InvoiceBigDecimal 并返回该发票的总价 .

    然后我获得 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 方法中的方法引用 .

  • 3

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

    Update

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

    如果您不介意第三方依赖项,则Eclipse Collections中有一个名为Collectors2的类,其中包含为summingsummarizing 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的提交者 .

  • 3

    您可以使用名为 summingUpreusable Collector来汇总 BigDecimal 流的值:

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

    Collector 可以像这样实现:

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

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

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

相关问题