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
Adding up BigDecimals using Streams
提问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 Stream
class has several methods
我注意到这个Stream
类有几个方法
Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong
Each of which has a convenient sum()
method. But, as we know, float
and double
arithmetic is almost always a bad idea.
每个都有一个方便的sum()
方法。但是,我们知道,float
和double
算术几乎总是一个坏主意。
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 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:
这两个答案都非常有帮助。我想补充一点:我的现实场景不涉及原始BigDecimal
s的集合,它们被包装在发票中。但是,我能够通过使用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:
它的作用是:
- Obtain a
List<BigDecimal>
. - Turn it into a
Stream<BigDecimal>
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 twoBigDecimal
's, via a method referenceBigDecimal::add
.
- 获得一个
List<BigDecimal>
. - 把它变成一个
Stream<BigDecimal>
调用reduce 方法。
3.1. 我们为加法提供一个身份值,即
BigDecimal.ZERO
。3.2. 我们通过方法引用指定
BinaryOperator<BigDecimal>
,它添加了两个。BigDecimal
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);
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.
除了我添加了一个totalMapper
变量,它有一个函数 from Invoice
toBigDecimal
并返回该发票的总价,它几乎是相同的。
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 map
method.
这里我们直接使用方法中的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 BigDecimal
stream using a reusableCollectornamed summingUp
:
您可以BigDecimal
使用名为的可重用收集器来总结流的值summingUp
:
BigDecimal sum = bigDecimalStream.collect(summingUp());
The Collector
can 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.
如果你不介意第三方的依赖,有一个名为类Collectors2在Eclipse中集合其中包含的方法返回收藏家的总结和概括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.
这可以防止在我们减少时尝试对空值求和。