单次迭代中的总和和最大值

sum and max values in a single iteration

我有一个自定义 CallRecord 对象的列表

public class CallRecord {

    private String callId;
    private String aNum;
    private String bNum;
    private int seqNum;
    private byte causeForOutput;
    private int duration;

    private RecordType recordType;
.
.
.
}

有两个逻辑条件,每个条件的输出是:

  1. 最高seqNum,sum(duration)
  2. 最高 seqNum,总和(持续时间),最高 causeForOutput

据我了解,Stream.max(), Collectors.summarizingInt() and so on will either require several iterations for the above result. I also came across a 建议自定义收集器,但我不确定。

下面是简单的 Java 8 之前的代码,用于此目的:

if (...) {

    for (CallRecord currentRecord : completeCallRecords) {
        highestSeqNum = currentRecord.getSeqNum() > highestSeqNum ? currentRecord.getSeqNum() : highestSeqNum;
        sumOfDuration += currentRecord.getDuration();
    }

} else {
    byte highestCauseForOutput = 0;

    for (CallRecord currentRecord : completeCallRecords) {
        highestSeqNum = currentRecord.getSeqNum() > highestSeqNum ? currentRecord.getSeqNum() : highestSeqNum;
        sumOfDuration += currentRecord.getDuration();

        highestCauseForOutput = currentRecord.getCauseForOutput() > highestCauseForOutput ? currentRecord.getCauseForOutput() : highestCauseForOutput;
        }

}

你想在一次迭代中完成所有事情的愿望是不合理的。你应该首先追求简单,必要时追求性能,但坚持单次迭代两者都不是。

性能取决于太多因素,无法提前做出预测。迭代(在一个普通集合上)本身的过程不一定是一个昂贵的操作,甚至可以从更简单的循环体中受益,这种循环体使得具有直接操作的多次遍历比尝试做所有事情的单次遍历更有效一次。唯一的方法就是用实际操作来衡量。

将操作转换为流操作可能会简化代码,如果您直接使用它,即

int highestSeqNum=
  completeCallRecords.stream().mapToInt(CallRecord::getSeqNum).max().orElse(-1);
int sumOfDuration=
  completeCallRecords.stream().mapToInt(CallRecord::getDuration).sum();
if(!condition) {
  byte highestCauseForOutput = (byte)
    completeCallRecords.stream().mapToInt(CallRecord::getCauseForOutput).max().orElse(0);
}

如果你仍然对多次迭代感到不舒服,你可以尝试编写一个自定义收集器一次执行所有操作,但结果不会比你的循环好,无论是在可读性还是在效率。

不过,与尝试在一个循环中完成所有操作相比,我更愿意避免代码重复,即

for(CallRecord currentRecord : completeCallRecords) {
    int nextSeqNum = currentRecord.getSeqNum();
    highestSeqNum = nextSeqNum > highestSeqNum ? nextSeqNum : highestSeqNum;
    sumOfDuration += currentRecord.getDuration();
}
if(!condition) {
    byte highestCauseForOutput = 0;
    for(CallRecord currentRecord : completeCallRecords) {
        byte next = currentRecord.getCauseForOutput();
        highestCauseForOutput = next > highestCauseForOutput? next: highestCauseForOutput;
    }
}

有了 Java-8,你可以用 Collector 解决它,没有多余的迭代。

通常,我们可以使用 Collectors 中的工厂方法,但在您的情况下,您需要实现自定义收集器,将 Stream<CallRecord> 缩减为 SummarizingCallRecord 的实例包含您需要的属性。

可变 accumulation/result 类型:

class SummarizingCallRecord {
  private int highestSeqNum = 0;
  private int sumDuration = 0;

  // getters/setters ...      
}

自定义收集器:

BiConsumer<SummarizingCallRecord, CallRecord> myAccumulator = (a, callRecord) -> {
  a.setHighestSeqNum(Math.max(a.getHighestSeqNum(), callRecord.getSeqNum()));
  a.setSumDuration(a.getSumDuration() + callRecord.getDuration());
};

BinaryOperator<SummarizingCallRecord> myCombiner = (a1, a2) -> {
  a1.setHighestSeqNum(Math.max(a1.getHighestSeqNum(), a2.getHighestSeqNum()));
  a1.setSumDuration(a1.getSumDuration() + a2.getSumDuration());
  return a1;
};

Collector<CallRecord, SummarizingCallRecord, SummarizingCallRecord> myCollector = 
  Collector.of(
    () -> new SummarizinCallRecord(), 
    myAccumulator, 
    myCombiner,
    // Collector.Characteristics.CONCURRENT/IDENTITY_FINISH/UNORDERED
  );

执行示例:

List<CallRecord> callRecords = new ArrayList<>();
callRecords.add(new CallRecord(1, 100));
callRecords.add(new CallRecord(5, 50));
callRecords.add(new CallRecord(3, 1000));


SummarizingCallRecord summarizingCallRecord  = callRecords.stream()
  .collect(myCollector);

// Result:
//    summarizingCallRecord.highestSeqNum = 5
//    summarizingCallRecord.sumDuration = 1150

您不需要也不应该通过 Stream API 实现逻辑,因为传统 for-loop 已经足够简单,而 Java 8 Stream API 可以'不要让它更简单:

int highestSeqNum = 0;
long sumOfDuration = 0;
byte highestCauseForOutput = 0; // just get it even if it may not be used. there is no performance hurt.
for(CallRecord currentRecord : completeCallRecords) {
    highestSeqNum = Math.max(highestSeqNum, currentRecord.getSeqNum());
    sumOfDuration += currentRecord.getDuration();
    highestCauseForOutput = Math.max(highestCauseForOutput, currentRecord.getCauseForOutput());
}

// Do something with or without highestCauseForOutput.