本文将展示3种,Java中通过Map的值获取其键的方式。本文将讨论不同方法的优缺点。如果想学习Map的更多内容,参见The Java HashMap Under the Hood
一、Java API
方法1: 迭代方式
Java 集合框架的Map类提供了 entrySet()方法,该方法返回Map的键值对Entry对象。
该方法的思路是,迭代Entry集合,当值和传入的value匹配时,返回对应的key。
public <K, V> K getKey(Map<K, V> map, V value) {
for (Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
return entry.getKey();
}
}
return null;
}
然而,有可能有多个键对应同一个值。因此我们找到匹配的值时需要将其加入到Set中,Set包含所有待查找的Key。
public <K, V> Set<K> getKeys(Map<K, V> map, V value) {
Set<K> keys = new HashSet<>();
for (Entry<K, V> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
keys.add(entry.getKey());
}
}
return keys;
}
尽管这种方式非常简单而直接,但是采用这种方式即使经过几次迭代就可以找到所有的键也得迭代完整个Map。
方法2: 函数式查找
我可以采用Java8的Lambda表达式,来更灵活和可读地方式实现类似功能。
我们可以使用Stream的map函数,返回满足条件的Entry的键。
public <K, V> Stream<K> keys(Map<K, V> map, V value) {
return map
.entrySet()
.stream()
.filter(entry -> value.equals(entry.getValue()))
.map(Map.Entry::getKey);
}
返回键的Stream是为了方便后续多样化的处理方式。调用者或许只需要一个或者所有指向某个值的键。因为Stream是惰性求值的,调用方可以根据需要控制迭代的次数。
另外,使用合适的收集器(collector)可以将返回值转换成需要的集合形式。
Stream<String> keyStream1 = keys(capitalCountryMap, "South Africa");
String capital = keyStream1.findFirst().get();
Stream<String> keyStream2 = keys(capitalCountryMap, "South Africa");
Set<String> capitals = keyStream2.collect(Collectors.toSet());
二、利用Apache Commons Collections库
如果需要多次调用上述方法来查询某个值对应的键,会造成不必要的多次迭代。
在这种场景下,维护另外一个值指向键的map就很有必要了,因为这样可以使通过值获取键的时间复杂度降为常数级。
Apache 的Commons Collections 库里提供了双向Map叫BidiMap。该类提供了getKey函数来根据值获取键。
BidiMap<String, String> capitalCountryMap = new DualHashBidiMap<>();
capitalCountryMap.put("Berlin", "Germany");
capitalCountryMap.put("Cape Town", "South Africa");
String capitalOfGermany = capitalCountryMap.getKey("Germany");
然而,BidiMap强制键值对时一一对应关系。如果键值对的值已经存在map中,你调用put方法,将会移除旧的entry对象。换句话说,该类是依据值来更新键的。
另外,该功能需要大量内存来存放反向map。
更多关于BidiMap的详细内容,参考:Apache Commons Collections BidiMap | Baeldung
三、使用 Google Guava
我们还可以使用Google Guava包下的一个叫BiMap的双向map。该类提供了Inverser()函数来获取值-键对。
HashBiMap<String, String> capitalCountryMap = HashBiMap.create();
capitalCountryMap.put("Berlin", "Germany");
capitalCountryMap.put("Cape Town", "South Africa");
String capitalOfGermany = capitalCountryMap.inverse().get("Germany");
和BidiMap一样,BiMap也不允许通过相同的值获取多个键。如果你这么做,会得到ava.lang.IllegalArgumentException异常。
不用说,BiMap也使用了大量内存来存储反向map。如果你对BiMap感兴趣,可以戳这里:Guide to Google's Guava BiMap | Baeldung
结论
本文简要讨论了通过键获取Map的值的方式。每种方法都有各自优缺点。我们要根据使用场景来选择最合适的方式。
完整源代码参见这里:https://github.com/eugenp/tutorials/tree/master/java-collections-maps