1. 引言

自从Java 8发布以来,流(Stream)已成为Java中的核心组件。它们提供了一种强大且优雅的数据处理方式。因此,在某些情况下,我们可能需要将流中的元素转换为MapMultimap

在本教程中,我们将深入研究使用不同的方法和库在Java中将流转换为MapMultimap的各种方式。

2. 流到Map的转换

2.1. 使用Collectors.toMap()

要将流转换为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);
}

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

2.2. 使用Stream.reduce()

我们也可以使用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);
}

请注意,Stream.reduce()操作符在遇到相同键的重复值时会出现。它会以不同于上一节的方式对其进行累积。它不会用最后遇到的值覆盖现有值,而是为该键创建一个值列表。

这就是为什么在结果映射中,“two”映射到包含两个“two”的列表,而在第2.1节中,它们是通过逗号连接的。

3. 流到Multimap的转换

3.1. 使用Guava的Multimap

Google的Guava库中,有一个Multimap接口,它将特定的键映射到多个值。首先,我们需要将其作为依赖项添加到我们的项目中:

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

然后,我们可以使用它将流转换为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);
}

在这段代码中,我们使用ArrayListMultimap::create方法收集stringStream元素。此外,(map, element) -> map.put(element, element)遍历流元素,将每个元素放入multimap中。这确保了multimap中的键和值都相同,保持重复条目的完整性。第三个函数ArrayListMultimap::putAll如果需要,可以将多个多映射结果合并到一个。

3.2. 使用Stream.reduce()

另一种将流转换为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);
}

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

3. 总结

总的来说,Java流提供了高效的数据处理能力,本教程涵盖了Collectors.toMap()Stream.reduce()以及Guava的Multimap方法,用于将流转换为MapMultimap。这些方法使我们能够在Java中有效地处理数据,为我们项目的需要提供了灵活性。

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