Java 8 Stream add elements to list and sum

2019-02-18 10:11发布

问题:

I believe I can do next using one stream operation on listOfPricedObjects:

List<BigDecimal> myList = new ArrayList();
myList = listOfPricedObjects.stream().map(PricedObject::getPrice).collect(Collectors.toList());
BigDecimal sum = listOfPricedObjects.stream().map(PricedObject::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add)

How I can fill myList with prices and calculate sum of prices using stream one time? Thanks

UPD: As the result I need myList filled with prices and sum variable with sum. But not with usding stream() twice for that.

回答1:

You can use peek and add to a new list while applying the reduction

List<BigDecimal> newList = new ArrayList<>();
BigDecimal sum = list.stream()
                     .map(PricedObject::getPrice)
                     .peek(newList::add)
                     .reduce(BigDecimal.ZERO, BigDecimal::add);

Please look at Tunaki answer if you interested in using a parallelStream with a non concurrent collection which makes sense since sum is an embarrassingly parallel task.



回答2:

What you want here is to collect your elements inside 2 collectors: the first one would collect into a list, and the second one would sum the price.

Since there are no such collectors in the Stream API itself, we can easily construct our own. Let's create a class ResultHolder that will hold the result of the Stream pipeline: this is the list of decimals and the sum.

class ResultHolder {
    List<BigDecimal> list = new ArrayList<>();
    BigDecimal sum = BigDecimal.ZERO;
}

Finally, we can use it with:

ResultHolder resultHolder = 
    listOfPricedObjects.stream()
            .map(PricedObject::getPrice)
            .collect(
                ResultHolder::new,
                (r, p) -> { r.list.add(p); r.sum = r.sum.add(p); },
                (r1, r2) -> { r1.list.addAll(r2.list); r1.sum = r1.sum.add(r2.sum); }
            );
System.out.println(resultHolder.list);
System.out.println(resultHolder.sum);

This will work in a parallel pipeline and will keep the initial order of the list, contrary to the other answers.



回答3:

While it might be reasonable to assume use cases where an arbitrary stream like listOfPricedObjects can’t be recreated from the source and hence, only traversed once, you can safely assume that traversing the list produced via Collectors.toList() can be traversed efficiently:

List<BigDecimal> myList = listOfPricedObjects.stream()
    .map(PricedObject::getPrice).collect(Collectors.toList());
BigDecimal sum = myList.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

There is no code duplication here and any attempt to perform these two unrelated operations in one stream traversal will make the code more complicated without any benefit.



回答4:

You could do a identity mapping a add the passthrough value to the list

BigDecimal sum = listOfPricedObjects.stream()
                                    .map(o -> {
                                         myList.add(o); 
                                         return o;})
                                    .map(PricedObject::getPrice)
                                    .reduce(BigDecimal.ZERO, BigDecimal::add)

But I'd go for Sleiman Jneidi's solution using peek(), its more elegant