【问题标题】:Java 8 streams groupby and count multiple propertiesJava 8 流 groupby 并计算多个属性
【发布时间】:2016-06-30 00:34:59
【问题描述】:

我有一个对象 Process,它有一个日期和一个布尔错误指示器。我想获得每个日期的总进程数和有错误的进程数。因此,例如 Jun 01 将有计数 2、1; Jun 02 将有 1, 0 和 Jun 03 1, 1。我能够做到这一点的唯一方法是流式传输两次以获得计数。我尝试过实现自定义收集器,但没有成功。有没有一个优雅的解决方案来代替我笨拙的方法?

    final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    final List<Process> processes = new ArrayList<>();
    processes.add(new Process(sdf.parse("2016-06-01"), false));
    processes.add(new Process(sdf.parse("2016-06-01"), true));
    processes.add(new Process(sdf.parse("2016-06-02"), false));
    processes.add(new Process(sdf.parse("2016-06-03"), true));

    System.out.println(processes.stream()
          .collect(
                  Collectors.groupingBy(Process::getDate, Collectors.counting()) ));

    System.out.println(processes.stream().filter(order -> order.isHasError())
              .collect(
                      Collectors.groupingBy(Process::getDate, Collectors.counting()) ));

private class Process  {
    private Date date;
    private boolean hasError;

    public Process(Date date, boolean hasError) {
        this.date = date;
        this.hasError = hasError;
    }

    public Date getDate() {
        return date;
    }

    public boolean isHasError() {
        return hasError;
    }
}

在@glee8e 的解决方案和@Holger 的提示之后编写代码

Collector<Process, Result, Result> ProcessCollector = Collector.of(
        () -> Result::new,
        (r, p) -> {
            r.increment(0);
            if (p.isHasError()) {
                r.increment(1);
            }
        }, (r1, r2) -> {
            r1.add(0, r2.get(0));
            r1.add(1, r2.get(1));
            return r1;
});

Map<Date, Result> results = Processs.stream().collect(groupingBy(Process::getDate, ProcessCollector));
results.entrySet().stream().sorted(Comparator.comparing(Entry::getKey)).forEach(entry -> System.out
        .println(String.format("date = %s, %s", sdf.format(entry.getKey()), entry.getValue())));



private class Result {

    private AtomicIntegerArray array = new AtomicIntegerArray(2);

    public int get(int index) {
        return array.get(index);
    }

    public void increment(int index) {
        array.getAndIncrement(index);
    }

    public void add(int index, int delta) {
        array.addAndGet(index, delta);
    }

    @Override
    public String toString() {
        return String.format("totalProcesses = %d, totalErrors = %d", array.get(0), array.get(1));
    }
}

【问题讨论】:

  • 您也可以在第二行将类型从List弱化为Collection

标签: java java-stream collectors


【解决方案1】:

我们最好添加一个 POJO 来存储结果,否则组合函数可能看起来有点晦涩难懂。我将 POJO 声明为公开的,但如果您认为隐藏它更好,可以更改它。

public class Result {
     public int all, error;
}

主要代码:

// Add it somewhere in this file.
private static final Set <Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

//...
// This is main processing code
processes.stream().collect(collectingAndThen(groupingBy(Process::getDate, new Collector<Process, Result, Result> {
            @Override
            public Supplier<Result> supplier() {
                return Result::new;
            }

            @Override
            public BiConsumer<Process, Result> accumlator() {
                return (p, r) -> {
                    r.total++;
                    if (p.isHasError())
                        r.error++;
                };
            }

            @Override
            public BinaryOperator<Result> combiner() {
                return (r1, r2) -> {
                    r1.total += r2.total;
                    r1.error += r2.error;
                    return r1;
                };
            }

            @Override
            public Function<Result, Result> finisher() {
                return Function.identity();
            }

            @Override
            public Set<Characteristics> characteristics() {
                return CH_ID;
            }
})));

PS:我假设你有import static java.util.stream.Collectors

【讨论】:

  • 感谢 glee8e。您的解决方案给出了真/假的计数。我正在寻找错误的总数和数量。所以对于 6 月 1 日,它给了我 1,1 对 1 有错误,1 没有。我想我可以在 lambda 表达式之外对其进行总结以获得总计数。但我真的很想在表达式中做到这一点。
  • 请注意Collector.of(…) 的存在,它允许您指定三个函数(省略标识完成器),而无需内部类。您甚至不必费心指定IDENTITY_FINISH 特征,该方法已经从缺少这样的功能中推断出这一点。但你可以考虑指定UNORDERED 特征……
  • 我使用@Holgers 提示来减少代码。这是它现在的样子
  • @Holger 好吧,我在 Collectors 类中寻找这样的功能,但没有运气......
  • 是的,它在interface Collector而不是实用程序class Collectors中,因为我们习惯于仅在所述实用程序类中搜索,所以很容易忽略它。我记得类似的在Integer 中搜索特定便利方法的失败,后来才知道它在Math 中(反之亦然)……
猜你喜欢
  • 1970-01-01
  • 2019-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-01
相关资源
最近更新 更多