Java Lambda表达式避免多次迭代

时间:2014-06-25 11:51:47

标签: java lambda java-8 java-stream

民间,

考虑以下示例,给定一个Trade对象列表,我的代码需要返回一个包含24小时,7天,30天和所有时间交易量的数组。

使用普通的旧迭代器,这只需要对集合进行一次迭代。

我尝试使用Java 8流和Lambda表达式来做同样的事情。我提出了这个代码,它看起来很优雅,工作正常,但需要在列表上进行4次迭代:

public static final int DAY = 24 * 60 * 60;

public double[] getTradeVolumes(List<Trade> trades, int timeStamp) {
    double volume = trades.stream().mapToDouble(Trade::getVolume).sum();
    double volume30d = trades.stream().filter(trade -> trade.getTimestamp() + 30 * DAY > timeStamp).mapToDouble(Trade::getVolume).sum();
    double volume7d = trades.stream().filter(trade -> trade.getTimestamp() + 7 * DAY > timeStamp).mapToDouble(Trade::getVolume).sum();
    double volume24h = trades.stream().filter(trade -> trade.getTimestamp() + DAY > timeStamp).mapToDouble(Trade::getVolume).sum();
    return new double[]{volume24h, volume7d, volume30d, volume};
}

如何在列表中仅使用一次迭代来实现相同的目标?

3 个答案:

答案 0 :(得分:9)

此问题类似于“摘要统计信息”收集器。看一下IntSummaryStatistics课程:

public class IntSummaryStatistics implements IntConsumer {
    private long count;
    private long sum;
    ...

    public void accept(int value) {
        ++count;
        sum += value;
        min = Math.min(min, value);
        max = Math.max(max, value);
   }

   ...

}

它旨在与collect()一起使用;这是IntStream.summaryStatistics()

的实现
public final IntSummaryStatistics summaryStatistics() {
    return collect(IntSummaryStatistics::new, IntSummaryStatistics::accept,
                   IntSummaryStatistics::combine);
}

写这样的Collector的好处是你的自定义聚合可以并行运行。

答案 1 :(得分:1)

感谢Brian,我最终实现了下面的代码,它并不像我希望的那样简单,但至少它只迭代一次,它的并行准备就绪,它通过我的单元测试。 欢迎任何改进的想法。

public double[] getTradeVolumes(List<Trade> trades, int timeStamp) {
    TradeVolume tradeVolume = trades.stream().collect(
            () -> new TradeVolume(timeStamp),
            TradeVolume::accept,
            TradeVolume::combine);
    return tradeVolume.getVolume();
}

public static final int DAY = 24 * 60 * 60;

static class TradeVolume {

    private int timeStamp;
    private double[] volume = new double[4];

    TradeVolume(int timeStamp) {
        this.timeStamp = timeStamp;
    }

    public void accept(Trade trade) {
        long tradeTime = trade.getTimestamp();
        double tradeVolume = trade.getVolume();
        volume[3] += tradeVolume;
        if (!(tradeTime + 30 * DAY > timeStamp)) {
            return;
        }
        volume[2] += tradeVolume;
        if (!(tradeTime + 7 * DAY > timeStamp)) {
            return;
        }
        volume[1] += tradeVolume;
        if (!(tradeTime + DAY > timeStamp)) {
            return;
        }
        volume[0] += tradeVolume;
    }

    public void combine(TradeVolume tradeVolume) {
        volume[0] += tradeVolume.volume[0];
        volume[1] += tradeVolume.volume[1];
        volume[2] += tradeVolume.volume[2];
        volume[3] += tradeVolume.volume[3];
    }

    public double[] getVolume() {
        return volume;
    }
}

答案 2 :(得分:0)

有可能使用Collectors.groupingBy方法对数据进行分区,但方程式会很复杂而且没有意图揭示。

由于getTimestamp()是一项昂贵的操作,因此最好将其保留为Java 8之前的迭代,因此您只需按Trade计算一次值。

仅仅因为Java 8添加了闪亮的新工具,不要试图把它变成锤子来锤击所有钉子。