4
class Stock{
   double profit;
   double profitPercentage;
   public double getProfit(){
      return profit;
   }
   public double getProfitPercentage(){
      return profitPercentage;
   }
}
List<Stock> stocks = getAllStocks();
stocks.stream.collect(Collectors.summarizingDouble(Stock:getProfit)).getSum();
stocks.stream.collect(Collectors.summarizingDouble(Stock:getProfitPercentage)).getSum();

I could not find out way to do in single pass of stream. Any help or pointer would be good.

Tunaki
  • 132,869
  • 46
  • 340
  • 423

2 Answers2

2

The straight-forward way is to create a custom collector class.

public class StockStatistics {

    private DoubleSummaryStatistics profitStat = new DoubleSummaryStatistics();
    private DoubleSummaryStatistics profitPercentageStat = new DoubleSummaryStatistics();

    public void accept(Stock stock) {
        profitStat.accept(stock.getProfit());
        profitPercentageStat.accept(stock.getProfitPercentage());
    }

    public StockStatistics combine(StockStatistics other) {
        profitStat.combine(other.profitStat);
        profitPercentageStat.combine(other.profitPercentageStat);
        return this;
    }

    public static Collector<Stock, ?, StockStatistics> collector() {
        return Collector.of(StockStatistics::new, StockStatistics::accept, StockStatistics::combine);
    }

    public DoubleSummaryStatistics getProfitStat() {
        return profitStat;
    }

    public DoubleSummaryStatistics getProfitPercentageStat() {
        return profitPercentageStat;
    }

}

This class serves as a wrapper around two DoubleSummaryStatistics. It delegates to them each time an element is accepted. In your case, since you're only interested in the sum, you could even use a Collectors.summingDouble instead of DoubleSummaryStatistics. Also, it returns the two statistics with getProfitStat and getProfitPercentageStat; alternatively, you could add a finisher operation that would return a double[] containing only both sums.

Then, you can use

StockStatistics stats = stocks.stream().collect(StockStatistics.collector());
System.out.println(stats.getProfitStat().getSum());
System.out.println(stats.getProfitPercentageStat().getSum());

A more generic way is to create a collector capable of pairing other collectors. You can use the pairing collector written in this answer and, also available in the StreamEx library.

double[] sums = stocks.stream().collect(MoreCollectors.pairing(
    Collectors.summingDouble(Stock::getProfit),
    Collectors.summingDouble(Stock::getProfitPercentage),
    (sum1, sum2) -> new double[] { sum1, sum2 }
));

The sum of the profit will be in sums[0] and the sum of the profit percentage will be in sums[1]. In this snippet, only the sums are kept and not the whole stats.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
0

You may try to use Collectors.teeing available since Java 12:

double[] res =
    getAllStocks().stream()
        .collect(
            Collectors.teeing(
                Collectors.summingDouble(Stock::getProfit),
                Collectors.summingDouble(Stock::getProfitPercentage),
                (profitSum, percentageSum) -> new double[] {profitSum, percentageSum}));
Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90