【问题标题】:how to use java stream to group fields and create a summary如何使用 java 流对字段进行分组并创建摘要
【发布时间】:2026-01-25 20:50:01
【问题描述】:
public class Call {
    private String status;
    private String callName;
}

我有一个通话清单,我必须创建一个摘要,如下所示:

public class CallSummary {
    private String callName;
    private List<ItemSummary> items;
}
public class itemSummary {
    private String status;
    private Integer percentage;
}

我的目标是显示具有某种状态的电话的百分比 喜欢:

INBOUND_CALL : {
FAILED = 30%
SUCCESS = 70%
}

如何使用 java 8 流和收集器来做到这一点?

【问题讨论】:

    标签: java java-stream collectors


    【解决方案1】:

    分组背后的想法是嵌套是这样一种方式,即您有一个调用名称,然后可以使用基于状态的计数查找。我还建议对状态使用枚举

    enum CallStatus {
        FAILED, SUCCESS
    }
    

    并在其他类中调整为

    class Call {
        private CallStatus status;
        private String callName;
    }
    

    然后您可以实现嵌套分组并从中间结果开始,例如:

    List<Call> sampleCalls = List.of(new Call(CallStatus.SUCCESS,"naman"),new Call(CallStatus.FAILED,"naman"),
            new Call(CallStatus.SUCCESS,"diego"), new Call(CallStatus.FAILED,"diego"), new Call(CallStatus.SUCCESS,"diego"));
    
    Map<String, Map<CallStatus, Long>> groupedMap = sampleCalls.stream()
            .collect(Collectors.groupingBy(Call::getCallName,
                    Collectors.groupingBy(Call::getStatus, Collectors.counting())));
    

    这会给你一个输出

    {diego={FAILED=1, SUCCESS=2}, naman={FAILED=1, SUCCESS=1}}
    

    您还可以进一步评估百分比。 (尽管在Integer 中表示它们可能会失去精确度,具体取决于您如何进一步评估它们。)


    为了进一步解决这个问题,您可以保留另一个 Map 用于基于名称的计数查找:

    Map<String, Long> nameBasedCount = calls.stream()
            .collect(Collectors.groupingBy(Call::getCallName, Collectors.counting()));
    

    进一步,计算CallSummaryCallSummary 类型的摘要为:

    List<CallSummary> summaries = groupedMap.entrySet().stream()
            .map(entry -> new CallSummary(entry.getKey(), entry.getValue().entrySet()
                    .stream()
                    .map(en -> new ItemSummary(en.getKey(), percentage(en.getValue(),
                            nameBasedCount.get(entry.getKey()))))
                    .collect(Collectors.toList()))
            ).collect(Collectors.toList());
    

    其中percentage 计数由您使用签名int percentage(long val, long total)ItemSummary 中选择的数据类型对齐。

    示例结果:

    [
    CallSummary(callName=diego, items=[ItemSummary(status=FAILED, percentage=33), ItemSummary(status=SUCCESS, percentage=66)]), 
    CallSummary(callName=naman, items=[ItemSummary(status=FAILED, percentage=50), ItemSummary(status=SUCCESS, percentage=50)])
    ]
    

    【讨论】:

      【解决方案2】:

      以下收集到一个状态 -> 百分比图,然后您可以将其转换为您的输出模型。此代码采用getStatus 方法。

      List<Call> calls;
      Map<String,Double> statusPercents = calls.stream()
          .collect(Collectors.groupingBy(Call::getStatus,
              Collectors.collectingAndThen(Collectors.counting(), 
                  n -> 100.0 * n / calls.size())));
      

      我意识到这段代码有点难以阅读。收集器链按状态对调用进行分组,然后对每个组进行计数,最后转换为百分比。您可以(可以说)通过为收集器设置临时变量来使其更具可读性:

      var percentFunction = n -> 100.0 * n / calls.size();
      var collectPercent = collectingAndThen(count(), percentFunction);
      var collectStatusPercentMap = groupingBy(Call::getStatus, collectPercent);
      

      您还想按呼叫名称进行分组,但这实际上是一回事 - 使用 groupingBy,然后将呼叫列表减少到 CallSummary

      【讨论】: