1. Introduction
In this quick tutorial, we’re going to demonstrate three different approaches for retrieving the key from a map for a given value. We’ll also discuss the positives and negatives of the various solutions.
To learn more about the Map interface, you can check out this article.
2. An Iterative Approach
The Map interface of Java Collections offers a method called entrySet(). It returns all the entries or key-value pairs of the map in a Set.
The idea is to iterate over this entry-set and return the key for which the value matches the supplied value:
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;
}
However, there might be a possibility that multiple keys are pointing to the same value.
In that case, if a matching value is found, we add the key to a Set and continue the loop. In the end, we return the Set containing all the desired keys:
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;
}
Although this is a very straight-forward implementation, it compares all the entries even if all matches are found after a few iterations.
3. A Functional Approach
With the introduction of Lambda Expressions in Java 8, we can do it in a more flexible and readable way. We convert the entry-set to a Stream and supply a lambda to filter only those entries with the given value.
Then we use the map method to return a Stream of the keys from the filtered entries:
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);
}
The advantage of returning a stream is that it can cater to a wide range of client needs. The calling code may require only one key or all the keys pointing to the supplied value. As the evaluation of a stream is lazy, the client can control the number of iteration based on its requirement.
Additionally, the client may convert the stream to any collection using an appropriate 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());
4. Using Apache Commons Collections
The above ideas wouldn’t be very helpful if we need to call the functions very frequently for a particular map. It will unnecessarily iterate the set of its keys again and again.
In this scenario, maintaining another map of value to the keys would make more sense as it will take constant time to retrieve the key for a value.
The Commons Collections library by Apache provides with such a bi-directional Map called BidiMap. It has a method named getKey() for retrieving a key for a given value:
BidiMap<String, String> capitalCountryMap = new DualHashBidiMap<>();
capitalCountryMap.put("Berlin", "Germany");
capitalCountryMap.put("Cape Town", "South Africa");
String capitalOfGermany = capitalCountryMap.getKey("Germany");
However, BidiMap imposes a 1:1 relationship between its keys and values. If we try to put a key-value pair for which the value already exists in the Map, it removes the old entry. In other words, it updates the key against the value.
Also, it requires a larger amount of memory for keeping the reverse map.
More details on how to use a BidiMap are in this tutorial.
5. Using Google Guava
We may use another bi-directional Map called BiMap found in Guava developed by Google. This class provides a method named inverse() to get the value-key Map or the reverse Map to fetch the key based on a given value:
HashBiMap<String, String> capitalCountryMap = HashBiMap.create();
capitalCountryMap.put("Berlin", "Germany");
capitalCountryMap.put("Cape Town", "South Africa");
String capitalOfGermany = capitalCountryMap.inverse().get("Germany");
Like BidiMap, BiMap also doesn’t allow multiple keys referring to the same value. If we try to make such an attempt, it throws a java.lang.IllegalArgumentException.
Needless to say, BiMap also uses a significant amount of memory as it has to store the inverse map inside. If you are interested to know more about BiMap, you can check out this tutorial.
6. Conclusion
In this brief article, we’ve discussed some methods of retrieving a Map’s key given the value. Each approach has its own pros and cons. We should always consider the use-cases and choose the most appropriate one based on the situation.
The complete source code for the above tutorial is available over on GitHub.