Подсчет элементов потока

Я хочу подсчитать различные элементы потока, и мне интересно, почему

Stream<String> stream = Stream.of("a", "b", "a", "c", "c", "a", "a", "d");
Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, 1, Integer::sum));

не работает. Затмение говорит мне

Метод toMap(Function, Function, BinaryOperator) в типе Collectors неприменим для аргументов (( s) -> {}, int, Integer::sum)

Кстати, я знаю об этом решении:

Map<String, Long> counter2 = stream.collect(Collectors.groupingBy(s -> s, Collectors.counting()));

Итак, у меня есть два вопроса:

  1. В чем ошибка моего первого подхода?
  2. Как бы вы реализовали такой счетчик?

EDIT: я сам решил первый вопрос:

Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, s -> 1, Integer::sum)); 

Java ожидает функцию в качестве второго аргумента.


person principal-ideal-domain    schedule 26.05.2015    source источник
comment
Если вы решили вопрос самостоятельно, просто напишите ответ, а не редактируйте вопрос.   -  person Tagir Valeev    schedule 26.05.2015
comment
@TagirValeev Это только первая часть. Разве ответ не должен отвечать на полный вопрос? Я также хочу, чтобы люди больше не беспокоились о том, что я сделал что-то не так.   -  person principal-ideal-domain    schedule 26.05.2015
comment
stackoverflow.com/questions/25441088 /   -  person Nitin Dandriyal    schedule 26.05.2015
comment
@NitinDandriyal Это именно второй подход.   -  person principal-ideal-domain    schedule 26.05.2015
comment
Если вам нужен Map<String, Integer>, вы можете использовать .collect(groupingBy(s -> s, summingInt(s -> 1)));, хотя подход counting() более удобочитаем.   -  person Alexis C.    schedule 26.05.2015


Ответы (3)


Есть действительно несколько способов сделать это. Тот, кого вы не упомянули, это .collect(groupingBy(x -> x, summingInt(x -> 1)));

Есть некоторые отличия в производительности.

Подход № 1 лучше всего подходит, если в корзине очень мало объектов. В идеальном случае, когда на ведро приходится только 1 объект, вы сразу же получаете окончательную карту без необходимости изменять записи. В худшем случае с очень большим количеством повторяющихся объектов ему придется много упаковывать/распаковывать.

Подход № 2 основан на сборщике counting(), который не указывает, как именно он должен выполнять подсчет. Текущая реализация перенаправляет на reducing, но это может измениться.

Подход summingInt будет накапливать счетчик в int, а не в Integer, и, таким образом, не потребует упаковки/распаковки. В лучшем случае объекты будут повторяться очень большое количество раз.

Что касается того, какой из них выбрать, лучше всего кодировать для ясности и оптимизировать, когда это станет необходимо. Для меня groupingBy(x->x, counting()) наиболее четко выражает намерение, так что я бы предпочел именно его.

person Misha    schedule 26.05.2015

    private Collector<CustomObject.class, int[], Integer> customIntegerCountingCollector() {
    return Collector.of(
        () -> new int[1],
        (result, ) -> result[0] += 1,
        (result1, result2) -> {
            result1[0] += result2[0];
            return result1;
        },
        total -> Integer.valueOf(total[0])
    );
}

Вдохновлено: https://www.deadcoderising.com/2017-03-07-java-8-creating-a-custom-collector-for-your-stream/

person Vishal Seshagiri    schedule 23.07.2020

Следующее должно работать, если вы не используете параллельные потоки:

final int[] counter = {0};
stream.peek(s -> counter[0]++);
person Roland    schedule 26.11.2019