使用 Streams 将 BigDecimals 相加
我有一组 BigDecimal(在本例中为 LinkedList
),我想将它们加在一起.是否可以为此使用流?
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?
我注意到 Stream
类有几个方法
I noticed the Stream
class has several methods
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
每个都有一个方便的 sum()
方法.但是,正如我们所知,float
和 double
算术几乎总是一个坏主意.
Each of which has a convenient sum()
method. But, as we know, float
and double
arithmetic is almost always a bad idea.
那么,有没有一种方便的方法来总结 BigDecimals?
So, is there a convenient way to sum up 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()
总结 BigDecimal,但这(如预期的那样)并不精确.
As you can see, I am summing up the BigDecimals using BigDecimal::doubleValue()
, but this is (as expected) not precise.
后人回答
这两个答案都非常有帮助.我想补充一点:我的真实场景不涉及原始 BigDecimal
的集合,它们包含在发票中.但是,我可以通过使用 map()
函数来修改 Aman Agnihotri 的答案来解决这个问题:
Both answers were extremely helpful. I wanted to add a little: my real-life scenario does not involve a collection of raw BigDecimal
s, 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:
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;
}
}
推荐答案
原答案
是的,这是可能的:
Original answer
Yes, this is possible:
List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
它的作用是:
- 获取一个
List<BigDecimal>
. - 把它变成一个
Stream<BigDecimal>
调用reduce方法.
- Obtain a
List<BigDecimal>
. - Turn it into a
Stream<BigDecimal>
Call the reduce method.
3.1.我们提供一个用于加法的标识值,即 BigDecimal.ZERO
.
3.1. We supply an identity value for addition, namely BigDecimal.ZERO
.
3.2.我们指定 BinaryOperator
,它通过方法引用 BigDecimal::add
添加两个 BigDecimal
.
3.2. We specify the BinaryOperator<BigDecimal>
, which adds two BigDecimal
's, via a method reference BigDecimal::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);
除了我添加了一个 totalMapper
变量之外,它几乎是相同的,它具有从 Invoice
到 BigDecimal
的函数并返回该发票的总价.
It is mostly the same, except that I have added a totalMapper
variable, that has a function from Invoice
to BigDecimal
and returns the total price of that invoice.
然后我获得一个Stream
,将其映射到一个Stream
BigDecimal
.
Then I obtain a Stream<Invoice>
, map it to a Stream<BigDecimal>
and then reduce it to a BigDecimal
.
现在,从 OOP 设计的角度来看,我建议您也实际使用您已经定义的 total()
方法,这样会变得更容易:
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:
List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
.map(Invoice::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
这里我们直接使用map
方法中的方法引用.
Here we directly use the method reference in the map
method.
相关文章