1. 引言
自从Java 8发布以来,流(Stream)已成为Java中的核心组件。它们提供了一种强大且优雅的数据处理方式。因此,在某些情况下,我们可能需要将流中的元素转换为Map或Multimap。
在本教程中,我们将深入研究使用不同的方法和库在Java中将流转换为Map或Multimap的各种方式。
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
方法,用于将流转换为Map和Multimap。这些方法使我们能够在Java中有效地处理数据,为我们项目的需要提供了灵活性。
如往常一样,本文的完整代码示例可以在GitHub上找到。