在 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 更加灵活和强大。
