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

时间:2017-11-03 07:52:56

标签: java-8 java-stream collectors

我有一个自定义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,总和(持续时间)
  2. 最高seqNum,总和(持续时间),最高causeForOutput
  3. 根据我的理解,Stream.max()Collectors.summarizingInt()等将要求对上述结果进行多次迭代。我也遇到了thread建议定制收藏家,但我不确定。

    以下是用于此目的的简单的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;
            }
    
    }
    

3 个答案:

答案 0 :(得分:5)

您希望在一次迭代中完成所有事情是不合理的。你应该首先考虑简单性,必要时要提高性能,但坚持单次迭代既不是。

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

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

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;
    }
}

答案 1 :(得分:2)

使用Java-8,您可以使用Collector解决它而不使用redudant迭代。

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

可变积累/结果类型:

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

答案 2 :(得分:0)

您不需要也不应该通过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.