Introduction
Map
implementations in Java represent structures that map keys to values. A Map
cannot contain duplicate keys and each can at most be mapped to one value. The Map<K,V>
implementations are generic and accept any K
(key) and V
(value) to be mapped.
The Map
interface also includes methods for some basic operations (such as put()
, get()
, containsKey()
, containsValue()
, size()
, etc.), bulk operations (such as putAll()
and clear()
) and collection views (such as keySet()
, entrySet()
and values()
).
The most prominent Map
implementations used for general purposes are: HashMap
, TreeMap
and LinkedHashMap
.
In this article, we'll take a look at how to filter a Map by its keys and values:
- Filter a Map with enhanced for-loops
- Filter a Map by Keys with Stream.filter()
- Filter a Map by Values with Stream.filter()
Filter a Map with enhanced for-loops
Let's populate a HashMap
with some key-value pairs:
Map<Integer, String> employeeMap = new HashMap<>();
employeeMap.put(35, "Mark");
employeeMap.put(40, "John");
employeeMap.put(23, "Michael");
employeeMap.put(31, "Jim");
employeeMap.put(25, "Kevin");
The Map
has keys of type Integer
and values of type String
. They represent the age and name of the employees.
We'll filter this map by keys and values and save the results in a Collection
, such as another Map
implementation, or even another HashMap
.
Let's go with the LinkedHashMap
which preserves the order of insertion:
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> employee : employeeMap.entrySet()) {
if(employee.getKey() > 30){
linkedHashMap.put(employee.getKey(), employee.getValue());
}
}
System.out.println("Filtered Map: " + linkedHashMap);
Here, we've gone through the entrySet()
of the employeeMap
, and added each employee into a LinkedHashMap
via its put()
method. This would work the exact same for the HashMap
implementation, but it wouldn't preserve the order of insertion:
Filtered Map: {35=Mark, 40=John, 31=Jim}
Filtering out by values boils down to much the same approach, albeit, we'll be checking the value of each entry and using that in a condition:
Map<Integer, String> linkedHashMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> employee : employeeMap.entrySet()) {
if(employee.getValue().equals("Mark")){
linkedHashMap.put(employee.getKey(), employee.getValue());
}
}
System.out.println("Filtered Map: " + linkedHashMap);
And this would output:
Filtered Map: {35=Mark}
This is the manual way to filter a map - iterating and picking the desired elements. Let's now take a look at a more readable, and friendlier way - via the Stream API.
Stream.filter()
A more modern way to filter maps would be leveraging the Stream API from Java 8, which makes this process much more readable. The filter()
method of the Stream
class, as the name suggests, filters any Collection
based on a given condition.
For example, given a Collection
of names, you can filter them out based on conditions such as - containing certain characters or starting with a specific character.
Filter a Map by Keys with Stream.filter()
Let's leverage the Stream API to filter out this same map given the same condition. We'll stream()
the entrySet()
of the map, and collect()
it back into a Map
:
Map<Integer, String> filteredMap = employeeMap.entrySet()
.stream().filter(x->x.getKey() > 30)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println("Filtered map: " + filteredMap);
What this code does is much the same as what we did manually - for each element in the map's set, we check if their key's value is greater than 30
and collect the values into a new Map
, with their respective keys and values supplied through the getKey()
and getValue()
method references:
Filtered map: {35=Mark, 40=John, 31=Jim}
Filter a Map by Values with Stream.filter()
Now, let's populate another map, and instead of a <Integer, String>
key-value pair, we'll use a <String, String>
pair:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Map<String, String> cityMap = new HashMap<>();
cityMap.put("Tokyo", "Japan");
cityMap.put("Berlin", "Germany");
cityMap.put("Kyoto", "Japan");
cityMap.put("Belgrade", "Serbia");
cityMap.put("Madrid", "Spain");
This time around, we've got city-country pairs, where keys are individual cities, and the values are the countries they're located in. Values don't have to be unique. Kyoto
and Tokyo
, which are both unique keys can have the same value - Japan
.
Sorting this map by values, again, boils down to much the same approach as before - we'll simply use the value, via the getValue()
method in the filtering condition:
Map<String, String> filteredMap = citiesMap.entrySet()
.stream().filter(x->"Japan".equals(x.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println("Filtered map: " + filteredMap)
Now, this results in a filtered map that contains both Tokyo
and Kyoto
:
Filtered map: {Tokyo=Japan, Kyoto=Japan}
You can get creative with the outputs and results here. For example, instead of putting these elements into a new map and returning that - we can manipulate the resulting value into other structures as well. For example, we could filter out the keys what have Japan
and Serbia
as values, and join the keys into a single String
:
String filteredMap = citiesMap.entrySet()
.stream().filter(x-> x.getValue().equals("Japan") ||
x.getValue().equals("Serbia"))
.map(Map.Entry::getKey).collect(Collectors.joining(", "));
System.out.println("Filtered map: " + filteredMap);
Here, we've used a different Collector
than before. The Collectors.joining()
returns a new Collector
that joins the elements into a String
. Other than the CharSequence delimiter
we've passed in, we could've also supplied a CharSequence prefix
and CharSequence suffix
for each joined element.
This results in a String
, with all of the filtered elements, separated by a ,
:
Filtered map: Belgrade, Tokyo, Kyoto
Conclusion
In this article, we've taken a look at how to filter a Map
in Java. We've first gone over how to use enhanced for-loops for pre-Java 8 projects, after which we've dived into the Steam API and leveraged the filter()
method.
Filtering maps by either values or keys is rendered into a simple, one-liner task with the help of the Stream API, and you have a wide variety of Collector
s to format the output to your liking.