在 Java 中,Collectors.toMap
是一个非常方便的工具,用于将流(Stream)中的元素收集成一个 Map
。它的基本用法非常简单,但如果流中的元素存在重复的键,就会抛出 IllegalStateException
。这时候,我们可以通过提供一个合并函数来控制如何处理重复的键。
本文将探讨如何使用 Collectors.toMap
并处理重复键的几种常见方式。
基本用法
Collectors.toMap
的标准用法如下:
Map<KeyType, ValueType> map = stream.collect( Collectors.toMap( KeyType::getKey, // 键的提取方式 ValueType::getValue // 值的提取方式 ) );
在上面的代码中,stream
是一个流,KeyType::getKey
和 ValueType::getValue
分别是获取键和值的函数。这样每个元素都会被映射成一个键值对并放入 Map
中。
处理重复键
当流中有重复的键时,Collectors.toMap
会抛出 IllegalStateException
,因为 Map
中的键必须唯一。如果我们希望处理这种情况,可以通过第三个参数 mergeFunction
来定义合并重复键时的行为。
这个合并函数接受两个参数:existing
和 replacement
,分别代表已经存在的值和新出现的值。你可以根据自己的需求决定如何合并它们。
1. 忽略重复的键
如果我们希望在遇到重复的键时忽略新出现的值,只保留第一个值,可以使用一个始终返回 existing
的合并函数。
Map<KeyType, ValueType> map = stream.collect( Collectors.toMap( KeyType::getKey, // 键的提取方式 ValueType::getValue, // 值的提取方式 (existing, replacement) -> existing // 忽略新值,保留现有值 ) );
在这个例子中,合并函数 (existing, replacement) -> existing
表示:如果遇到重复键,就保留现有值 existing
,忽略新值 replacement
。
2. 合并重复的值
如果你希望在遇到重复的键时合并值(例如取两个值的最大值、最小值、相加等),可以自定义合并逻辑。以下是一个例子,合并重复键时选择较大的值:
Map<KeyType, Integer> map = stream.collect( Collectors.toMap( KeyType::getKey, KeyType::getValue, (existing, replacement) -> Math.max(existing, replacement) // 取较大的值 ) );
在这个例子中,合并函数 (existing, replacement) -> Math.max(existing, replacement)
用来选择较大的值作为结果。你可以根据自己的需求调整合并的方式,例如使用 Math.min
或 existing + replacement
来求和。
3. 自定义合并逻辑
除了选择最大值或最小值,我们还可以进行更复杂的合并操作。例如,如果值是一个集合类型,我们可能希望将重复键的值合并到一个集合中:
Map<KeyType, Set<ValueType>> map = stream.collect( Collectors.toMap( KeyType::getKey, v -> new HashSet<>(Collections.singletonList(v)), // 初始值为一个包含当前元素的集合 (existingSet, newSet) -> { existingSet.addAll(newSet); // 合并两个集合 return existingSet; } ) );
在这个例子中,我们将所有值合并成一个集合,重复键的值会被放入同一个集合中。
总结
使用 Collectors.toMap
时,遇到重复键的处理是一个常见的挑战。通过使用合并函数,我们可以灵活地处理这些情况。常见的处理方式包括:
- 忽略重复键:保留原有值,忽略新值。
- 合并值:根据业务需求选择合适的合并方式,如求最大值、最小值、相加等。
- 自定义合并逻辑:可以使用更复杂的逻辑,如合并集合、列表等数据结构。
掌握了这些技巧,你就可以根据不同场景自定义如何合并重复的键值对,从而使得 Collectors.toMap
更加灵活和强大。