У меня есть объектный процесс с датой и логическим индикатором ошибки. Я хочу получить общее количество процессов и количество процессов с ошибками для каждой даты. Так, например, 01 июня будет иметь счет 2, 1; 2 июня будет 1, 0, а июнь 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));
    }
}
1
golfradio 29 Июн 2016 в 05:19
Вы также можете ослабить тип с List до Collection во второй строке.
 – 
vphilipnyc
29 Июн 2016 в 05:47

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

4
Bilesh Ganguly 29 Июн 2016 в 07:34
Спасибо glee8e. Ваше решение дает количество истинных / ложных. Я искал общее количество и количество ошибок. Итак, 1 июня он дает мне 1, 1 за 1 с ошибкой и 1 без. Думаю, я могу суммировать это за пределами лямбда-выражения, чтобы получить общее количество. Но я действительно хотел сделать это внутри выражения.
 – 
golfradio
29 Июн 2016 в 05:58
2
Помните о существовании Collector.of(…), который позволяет указать три функции (без финишера идентичности) без необходимости во внутреннем классе. Вам даже не нужно беспокоиться об указании характеристики IDENTITY_FINISH, этот метод уже выведет это из-за отсутствия такой функции. Но вы можете рассмотреть возможность указания характеристики UNORDERED
 – 
Holger
29 Июн 2016 в 16:42
Я использовал совет @Holgers, чтобы немного сократить код. Вот как это выглядит сейчас
 – 
golfradio
29 Июн 2016 в 18:38
Ну, я искал такую ​​функцию в классе Collectors, но не повезло ...
 – 
glee8e
30 Июн 2016 в 13:04
Да, он находится в interface Collector, а не в служебной программе class Collectors, что легко упустить из виду, поскольку мы привыкли искать только в указанном служебном классе. Я помню похожие неудачные поиски определенного удобного метода в Integer, чтобы позже узнать, что он находится в Math (или наоборот)…
 – 
Holger
30 Июн 2016 в 13:20