Чтобы получить Map<String, List<String>>
, вам просто нужно сказать в groupingBy
коллектор, который вы хотите сгруппировать значения от идентичности, поэтому функция x -> x
.
Map<String, List<String>> occurrences =
streamOfWords.collect(groupingBy(str -> str));
Однако это немного бесполезно, поскольку вы видите, что у вас есть тот же тип информации два раза. Вы должны посмотреть в Map<String, Long>
, где значение указывает на появление строки в потоке.
Map<String, Long> occurrences =
streamOfWords.collect(groupingBy(str -> str, counting()));
В принципе, вместо того, чтобы иметь groupingBy
, которые возвращают значения, как List
, вы используете вниз по течению коллектора counting()
, чтобы сказать, что вы хотите, чтобы подсчитать, сколько раз появляется это значение.
Ваше требование рода должно означать, что вы должны иметь Map<Long, List<String>>
(что, если различные строки появляются одинаковое число раз?), А в качестве коллектора по умолчанию toMap
возвращает HashMap
, он не имеет понятия упорядоченности, но вы можете хранить элементы вместо TreeMap
.
Я попытался кратко изложить то, что я сказал в комментариях.
У вас возникли проблемы с тем, как str -> str
может определить, отличаются ли «привет» или «мир».
Прежде всего str -> str
- это функция, то есть для входа x значение f (x). Например, f(x) = x + 2
- это функция, которая для любого значения x
возвращает x + 2
.
Здесь мы используем функцию тождества, то есть f(x) = x
. Когда вы собираете элементы из конвейера в Map
, эта функция будет вызываться раньше, чтобы получить ключ от значения. Итак, в вашем примере у вас есть 3 элемента, для которых функция идентификации дает:
f("hello") = "hello"
f("world") = "world"
Пока все хорошо.
Теперь, когда вызывается collect()
, для каждого значения в потоке вы будете применять к нему функцию и оценивать результат (который будет ключом в Map
). Если ключ уже существует, мы берем текущее сопоставленное значение и объединяем в List
значение, которое мы хотели поставить (то есть значение, из которого вы только что применили функцию), с этим предыдущим сопоставленным значением. Вот почему вы получаете Map<String, List<String>>
в конце.
Возьмем еще один пример. Теперь поток содержит значения «hello», «world» и «hey», а функция, которую мы хотим применить для группировки элементов, - это str -> str.substring(0, 2)
, то есть функция, которая принимает первые два символа String.
Кроме того, мы имеем:
f("hello") = "he"
f("world") = "wo"
f("hey") = "he"
Здесь вы видите, что и «привет» и «эй» дает один и тот же ключ, при применении функции и, следовательно, они будут сгруппированы в одной и той же List
при сборе, так что конечный результат:
"he" -> ["hello", "hey"]
"wo" -> ["world"]
Чтобы иметь аналогию с математикой, вы могли бы принять любые не-биективные функции, такие как х . Для x = -2
и x = 2
у нас есть, что f(x) = 4
. Поэтому, если мы сгруппировали целые числа по этой функции, -2 и 2 были бы в одной и той же «сумке».
Глядя на исходный код, вы не сможете сразу понять, что происходит. Это полезно, если вы хотите знать, как это реализовано под капотом. Но сначала попробуйте подумать о концепции с более высоким уровнем абстракции, а затем, возможно, все станет понятнее.
Надеюсь, это поможет! :)
Спасибо, я дам вам принятый ответ, как только подтвержу это. P.S .: Не могли бы вы объяснить, как это работает? Особенно, какая стратегия, когда я хотел бы получать метаданные элементов в списке, когда я использую потоки? Как-то я, похоже, не понимаю. Я думаю, что это может быть полезно для будущих читателей. – SklogW
@SklogW Добавлено объяснение. Теперь понятно? – flo
Да, немного, еще одна мелочь: «s -> s» как выражение лямбда действительно достаточно, чтобы подразумевать, что мне нужны элементы с одинаковой идентичностью. Я не понимаю, как сказать функции, что мне нужны одни и те же элементы. s -> s.equals (s) был бы вздор, не так ли? – SklogW