Java 流 |按相同元素分组

Java Streams | groupingBy same elements

我有一个单词流,我想根据相同元素(=单词)的出现对它们进行排序。

例如:{你好,世界,你好}

Map<String, List<String>>

你好,{你好,你好}

世界,{世界}

我目前拥有的:

Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???));

问题一:stream好像丢失了他正在处理Strings的信息,所以编译器强制我改类型为Object,List

问题 2:我不知道在括号内放什么来按同一事件对它进行分组。我知道我能够处理 lambda 表达式中的单个元素,但我不知道如何到达 "outside" 每个元素以检查是否相等。

谢谢

您要查找的KeyExtractor是恒等函数:

Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity()));

编辑添加说明:

  • Function.identity() 使用一种方法返回 'Function',该方法仅返回其获取的参数。
  • Collectors.groupingBy(Function<S, K> keyExtractor)提供了一个收集器,它将流中的所有元素收集到一个Map<K, List<S>>。它使用它获得的 keyExtractor 实现来检查流的 S 类型的对象并从中推断出 K 类型的键。此键是地图的键,用于获取(或创建)流元素添加到的结果地图中的列表。

要获得 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()));

基本上,不是让 return 的 groupingBy 值为 List,而是使用下游收集器 counting() 来告诉您要计算次数出现此值。

您的排序要求应该意味着您应该有一个 Map<Long, List<String>>(如果不同的字符串出现相同次数怎么办?),并且作为默认的 toMap 收集器 returns HashMap,它没有排序的概念,但您可以将元素存储在 TreeMap 中。


我试图总结一下我在评论中所说的内容。

您似乎对 str -> str 如何判断 "hello" 或 "world" 是否不同感到困惑。

首先str -> str是一个函数,即对于输入x产生一个值f(x)。例如,f(x) = x + 2 是一个函数,对于任何值 x returns 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),即采用字符串的前两个字符。

同样,我们有:

f("hello") = "he"
f("world") = "wo"
f("hey") = "he"

在这里你看到 "hello" 和 "hey" 在应用函数时产生相同的键,因此在收集它们时它们将被分组在相同的 List 中,因此最终结果是:

"he" -> ["hello", "hey"]
"wo" -> ["world"]

要与数学进行类比,您可以采用任何非双射函数,例如 x2。对于 x = -2x = 2 我们有 f(x) = 4。所以如果我们用这个函数对整数进行分组,-2 和 2 就会在同一个 "bag".

一开始查看源代码并不能帮助您理解发生了什么。如果您想知道它是如何在引擎盖下实现的,它会很有用。但是首先尝试用更高的抽象层次来思考这个概念,然后事情可能会变得更清楚。

希望对您有所帮助! :)

如果你想按对象的某些字段而不是整个对象分组,并且你不想更改你的 equals 和 hashCode 方法,我会创建一个 class 保存一组键分组目的:

import java.util.Arrays;

@Getter
public class MultiKey {

    public MultiKey(Object... keys) {
        this.keys = keys;
    }

    private Object[] keys;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MultiKey multiKey = (MultiKey) o;
        return Arrays.equals(keys, multiKey.keys);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(keys);
    }

}

以及 groupingBy 本身:

Map<MultiKey, List<VhfEventView>> groupedList = list
        .stream()
        .collect(Collectors.groupingBy(
                 e -> new MultiKey(e.getGroupingKey1(), e.getGroupingKey2())));