1. 概述

在这篇简短的文章中,我们将探讨如何在Java中**反转一个Map**。目标是为给定的类型为Map<K, V>Map创建一个新的Map<V, K>实例。此外,我们还将讨论如何处理源映射中存在重复值的情况。

更多关于HashMap类的信息,请参考我们的另一篇文章。

2. 定义问题

假设我们有一个包含几个键值对的Map

Map<String, Integer> map = new HashMap<>();
map.put("first", 1);
map.put("second", 2);

原始的Map将存储这样的内容:

{first=1, second=2}

相反,我们希望将键反转成值,反之亦然,得到一个新的Map对象。结果将是:

{1=first, 2=second}

3. 使用传统for循环

首先,让我们看看如何使用**for循环来反转Map**:

public static <V, K> Map<V, K> invertMapUsingForLoop(Map<K, V> map) {
    Map<V, K> inversedMap = new HashMap<V, K>();
    for (Entry<K, V> entry : map.entrySet()) {
        inversedMap.put(entry.getValue(), entry.getKey());
    }
    return inversedMap;
}

在这里,我们遍历Map对象的entrySet()。然后,我们将原始值作为新键,原始键作为新值添加到inversedMap对象中。换句话说,我们通过替换键和值来复制映射的内容。这种方法适用于Java 8之前的版本,但请注意,这种方法仅在源映射的值唯一时才有效。

4. 使用Stream API反转Map

Java 8提供了Stream API中的便捷方法,以更函数式的方式反转Map。让我们来看看其中的一些方法。

4.1. 使用Collectors.toMap()

如果源映射中没有重复值,我们可以使用Collectors.toMap()

public static <V, K> Map<V, K> invertMapUsingStreams(Map<K, V> map) {
    Map<V, K> inversedMap = map.entrySet()
        .stream()
        .collect(Collectors.toMap(Entry::getValue, Entry::getKey));
    return inversedMap;
}

首先,将entrySet()转换为对象流。然后,我们使用Collectors.toMap()收集键和值到inversedMap对象中。

如果源映射包含重复值,我们可以使用映射函数来应用自定义规则到输入元素:

public static <K, V> Map<V, K> invertMapUsingMapper(Map<K, V> sourceMap) {
    return sourceMap.entrySet()
        .stream().collect(
            Collectors.toMap(Entry::getValue, Entry::getKey, (oldValue, newValue) -> oldValue) 
        );
}

在这个方法中,Collectors.toMap()的最后一个参数是一个映射函数。使用它,我们可以定制在值重复时应添加哪个键。在上述示例中,如果源映射包含重复值,我们保留第一个值作为键。然而,如果我们只想保留一个键,可以修改这个规则。

4.2. Collectors.groupingBy()

有时,即使源映射包含重复值,我们也可能需要所有键。在这种情况下,Collectors.groupingBy()提供了更好地处理重复值的控制。

例如,考虑以下键值对:

{first=1, second=2, two=2}

这里的值“2”对应不同的键重复两次。在这种情况下,我们可以使用groupingBy()方法执行对值对象的级联“分组”操作:

private static <V, K> Map<V, List<K>> invertMapUsingGroupingBy(Map<K, V> map) {
    Map<V, List<K>> inversedMap = map.entrySet()
        .stream()
        .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
    return inversedMap;
}

解释一下,Collectors.mapping()函数使用指定的收集器对给定键关联的值执行归约操作。groupingBy()收集器将重复值收集到一个列表中,从而得到一个多映射。输出现在将是:

{1=[first], 2=[two, second]}

5. 总结

在这篇文章中,我们快速回顾了几种内置方法,以及它们在例子中的使用,来反转HashMap。我们也看到了在反转Map对象时如何处理重复值。

同时,一些外部库在Map接口之上提供了额外的功能。我们之前已经演示了如何使用Google Guava的BiMapApache的BidiMap来反转Map

如往常一样,这些示例的代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-collections-maps-5