Convert a Stream into a Map or Multimap in Java – 用 Java 将数据流转换为地图或多地图

最后修改: 2023年 11月 7日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

Streams became integral for Java after the release of Java 8. They serve as potent, eloquent ways of processing data. As such, there are times when one may have to transform an element of a stream into a Map or Multimap.

在 Java 8 发布后成为 Java 不可或缺的一部分。它们是处理数据的有力、雄辩的方式。因此,有时可能需要将流元素转换为 Map Multimap

In this tutorial, we’ll have a deep look at the ways of transforming a stream into a Map or Multimap in Java using different approaches and libraries.

在本教程中,我们将深入了解在 Java 中使用不同方法和库将流转换为 MapMultimap 的方法。

2. Conversion of Stream to Map

2.将 Stream 转换为 Map</em

2.1. Using Collectors.toMap()

2.1.使用 Collectors.toMap()

To convert the stream into a Map, we can utilize the Collectors.toMap() function. Such a collector specifies the key-value mapping function, which maps every item in a stream accordingly. Here’s a basic example:

要将流转换为 Map , 我们可以使用 Collectors.toMap() 函数。这样的收集器会指定键值映射函数,从而相应地映射流中的每个项目。下面是一个基本示例:

@Test
public void givenStringStream_whenConvertingToMapWithMerge_thenExpectedMapIsGenerated() {
    Stream<String> stringStream = Stream.of("one", "two", "three", "two");

    Map<String, String> mergedMap = stringStream.collect(
      Collectors.toMap(s -> s, s -> s, (s1, s2) -> s1 + ", " + s2)
    );

    // Define the expected map
    Map<String, String> expectedMap = Map.of(
      "one", "one",
      "two", "two, two",
      "three", "three"
    );

    assertEquals(expectedMap, mergedMap);
}

The above test method starts by creating a Stream of strings stringStream, which is put into a Map using Collectors.toMap(). This function takes each string as its key and value, separated by commas, to consolidate multiple entries for the same key.

上述测试方法首先创建了一个字符串 StringStream 的 Stream,然后使用 Collectors.toMap() 将其放入 Map 中。该函数将每个字符串作为键和值,用逗号分隔,以合并同一键的多个条目。

2.2. Using Stream.reduce()

2.2.使用 Stream.reduce()

We may also use the Stream.reduce() operator. This method can help us construct the values of the stream into a Map using an identity and an accumulation function.

我们还可以使用 Stream.reduce() 操作符。该方法可帮助我们使用标识和累加函数将流的值构造成一个 Map

@Test
public void givenStringStream_whenConvertingToMapWithStreamReduce_thenExpectedMapIsGenerated() {
    Stream<String> stringStream = Stream.of("one", "two", "three", "two");

    Map<String, String> resultMap = stringStream.reduce(
      new HashMap<>(), (map, element) -> {
        map.put(element, element);
            return map;
        },
        (map1, map2) -> {
            map1.putAll(map2);
            return map1;
        }
    );

    Map<String, String> expectedMap = new HashMap<>();
    expectedMap.put("one", "one");
    expectedMap.put("two", "two");
    expectedMap.put("three", "three");

    assertEquals(expectedMap, resultMap);
}

Note that the Stream.reduce() operator encounters duplicate values for the same key. It accumulates them differently from the previous section. Instead of overwriting the existing value with the last encountered value, it aggregates them by creating a list of values for that key.

请注意,Stream.reduce() 操作符会遇到相同键值的重复值。它的累加方式与上一节不同。它不是用最后遇到的值覆盖现有值,而是通过为该键创建一个值列表来累加它们。

This is why “two” is mapped to a list containing two “two” values in the resulting map, whereas in section 2.1, it concatenated them with a comma.

这就是为什么在生成的映射中,”two“被映射为一个包含两个”two“值的列表,而在第 2.1 节中,它是用逗号将它们连接起来的。

3. Conversion of Stream to Multimap

3.将 Stream 转换为 Multimap</em

3.1. Using Guava’s Multimap

3.1.使用 Guava 的多地图</em

There is the Multimap interface that is found in Google’s Guava library, which maps a specific key to multiple values. First, we need to include it as a dependency in our project:

Google的Guava库中提供了Multimap接口,可将一个特定键映射到多个值。首先,我们需要在项目中将 它作为依赖项

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

Then, we utilize it to convert a stream into a ListMultimap as follows:

然后,我们利用它将数据流转换为 ListMultimap 如下:

@Test
public void givenStringStream_whenConvertingToMultimap_thenExpectedMultimapIsGenerated() {
    Stream<String> stringStream = Stream.of("one", "two", "three", "two");

    ListMultimap<String, String> multimap = stringStream.collect(
            ArrayListMultimap::create,
            (map, element) -> map.put(element, element),
            ArrayListMultimap::putAll
    );

    ListMultimap<String, String> expectedMultimap = ArrayListMultimap.create();
    expectedMultimap.put("one", "one");
    expectedMultimap.put("two", "two");
    expectedMultimap.put("two", "two");
    expectedMultimap.put("three", "three");

    assertEquals(expectedMultimap, multimap);
}

In the above code, we utilize the ArrayListMultimap::create method to collect the stringStream elements. Furthermore, the (map, element) -> map.put(element, element) iterates through the stream elements to put each element into the multimap. This ensures that both keys and values in the multimap are the same, maintaining the duplicate entries. The third function, ArrayListMultimap::putAll, combines multiple multimap results into one if needed.

在上述代码中,我们使用 ArrayListMultimap::create 方法来收集 stringStream 元素。此外,(map, element) -> map.put(element, element) 遍历流元素,将每个元素放入 multimap 中。这将确保多映射中的键和值相同,从而保持重复条目。第三个函数ArrayListMultimap::putAll会在需要时将多个多映射结果合并为一个。

3.2. Using Stream.reduce()

3.2.使用 Stream.reduce()

The other way of transforming a stream into a Multimap is by applying the reduce operation on the stream. In turn, this enables us to accomplish the conversion task through an identity value as well as an accumulation function:

将流转换为 Multimap 的另一种方法是在流上应用 reduce 操作。反过来,这又使我们能够通过标识值和累加函数来完成转换任务:

@Test
public void givenStringStream_whenConvertingToMultimapWithStreamReduce_thenExpectedMultimapIsGenerated() {
    Stream<String> stringStream = Stream.of("one", "two", "three", "two");

    Map<String, List<String>> multimap = stringStream.reduce(
      new HashMap<>(),
      (map, element) -> {
          map.computeIfAbsent(element, k -> new ArrayList<>()).add(element);
          return map;
      },
      (map1, map2) -> {
          map2.forEach((key, value) -> map1.merge(key, value, (list1, list2) -> {
              list1.addAll(list2);
              return list1;
          }));
          return map1;
      }
    );

    Map<String, List<String>> expectedMultimap = new HashMap<>();
    expectedMultimap.put("one", Collections.singletonList("one"));
    expectedMultimap.put("two", Arrays.asList("two", "two"));
    expectedMultimap.put("three", Collections.singletonList("three"));

    assertEquals(expectedMultimap, multimap);
}

Here, through the reduce operation, the test method accumulates the elements of stringStream into a multimap, where each unique string is mapped to a list of its occurrences. We also use lambda expressions to handle the mapping and merging of values and then ensure the correctness of the transformation through assertions.

在这里,通过 reduce 操作,测试方法将 stringStream 中的元素累积到 multimap 中,其中每个唯一字符串都被映射到其出现的列表中。我们还使用 lambda 表达式来处理值的映射和合并,然后通过断言来确保转换的正确性。

3. Conclusion

3.结论

In summary, Java streams offer efficient data processing, and this tutorial has covered methods like Collectors.toMap(), Stream.reduce(), and Guava’s Multimap for transforming streams into Map and Multimap. These methods empower us to handle data effectively in Java, providing flexibility to choose the right approach for our project’s needs.

总之,Java 流可提供高效的数据处理,本教程涵盖的方法包括 Collectors.toMap()Stream.reduce() 和用于将流转换为 MapMultimapGuava 的 Multimap。这些方法使我们能够在 Java 中有效地处理数据,并提供了根据项目需求选择正确方法的灵活性。

As always, the complete code samples for this article can be found on GitHub.

与往常一样,本文的完整代码示例可在 GitHub 上找到。