I thought I'd drop a couple of real-world examples of using the Map interface in Java, mostly because there are still a lot of old resources around that don't use modern Java. Hope it helps!
Create a map
Map<String, String> statesToCapitals = Map.of(
"Washington", "Olympia",
"Oregon", "Portland",
"California", "Sacramento",
"Idaho", "Boise",
"Nevada", "Carson City",
"Arizona", "Phoenix"
);
It's important to remember that maps created this way are immutable, and null can't be used as a key!
This creates a map as follows:
{Nevada=Carson City, California=Sacramento, Washington=Olympia, Idaho=Boise, Oregon=Portland, Arizona=Phoenix}
Iterate a map
statesToCapitals.forEach((key, value) -> {
System.out.println("The capital of " + key + " is " + value);
});
This would output:
The capital of Washington is Olympia
The capital of Idaho is Boise
The capital of Oregon is Portland
The capital of Arizona is Phoenix
The capital of Nevada is Carson City
The capital of California is Sacramento
Iterate Map entries
Besides the forEach
method directly available on the map, which allows you to pass a function to apply to its keys and/or values, you can also stream its entries and work with the Map.Entry
class.
For example, you can print the entries:
capitals.entrySet().stream().forEach(System.out::println);
This would output:
Washington=Olympia
Idaho=Boise
Oregon=Portland
Arizona=Phoenix
Nevada=Carson City
California=Sacramento
Convert to a collection of the maps values
Collection<String> capitalCities = statesToCapitals.values();
Note that depending on the map implementation being used, you could receive the values in unsorted or sorted order.
So you'd have something like:
[Sacramento, Carson City, Phoenix, Portland, Boise, Olympia]
Automatically retrieve a default value for missing entries
System.out.println("The capital of Wisconsin is " + statesToCapitals.getOrDefault("Wisconsin", "Don't know"));
Outputs:
The capital of Wisconsin is Don't know
Convert from a collection of objects to a lookup map for those objects
Let's say you started with a State object which represents a US state:
class State {
public String name() { // ... etc
public String capitalCity() { // ... etc
}
And if you have a collection of all US states:
Collection<State> usStates = usStates();
You can create a map to lookup each state by its name:
Map<String, State> nameToState = usStates.stream().collect(Collectors.toMap(State::name, Function.identity()));
In other words, you can now get California with nameToState.get("California")
and you will get the instance of State
associated with California.
Convert from a collection of objects to a lookup map on one of its values
Assume the same as above, but this time, you want to go from states to capitals--the map's key will be a value from the object, its name, like "California" or "Wisconsin", and the map's value will also be a value from the object, its capital, like "Sacramento" or "Madison."
Map<String, String> statesToCapitals = usStates.stream().collect(Collectors.toMap(State::name, State::capitalCity));
Handling maps of lists
Now let's consider if the State object also was able to return a collection of all major cities in a state:
class State {
public String name() { // ... etc
public String capitalCity() { // ... etc
public List<String> majorCities() { // ... etc
}
And you created a statesToCities
map similar to the above with capitals:
Map<String, List<String>> statesToCities = usStates.stream().collect(Collectors.toMap(State::name, State::majorCities));
The difference now is we have a Map<String, List<String>>
instead of a simpler Map<String, String>
like we had before. The "value" of the map is a list of cities. For example:
List<String> californiaCities = statesToCities.get("California");
System.out.println(californiaCities);
// outputs "[Sacramento, San Francisco, Los Angeles]"
Add a value to the list in a Map of Lists
In "old school Java", you had to check if a key existed in the map so that you could handle initializing the list if it wasn't there:
// deprecated way of adding new values
Map<String, List<String>> statesToCities = statesAndCitiesMap();
if(!statesToCities.containsKey("Montana")) {
statesToCities.put("Montana", new ArrayList<>());
}
statesToCities.get("Montana").add("Bozeman");
Now you can use computeIfAbsent
in place of get
to insure you start with the initial empty list even if nothing is there to begin with:
statesToCities.computeIfAbsent("Montana", initialValue -> new ArrayList<>()).add("Bozeman");
Create a count map
Here's a real world scenario that comes up all the time. What if we need a map to count the number of each type of something in a collection.
For this example, let's assume we have a Phone class with brand and model:
class Phone {
public String brand() { // etc ...
public String model() { // etc ...
}
Examples would be "Apple iPhone 11" or "Google Pixel 4".
And a collection of phones, let's say all the phones that Amazon offers for sale:
Collection<Phone> phones = phonesOnAmazon();
Then we just count them up:
Map<String, Long> numberOfPhonesPerBrand = phones.stream().collect(Collectors.groupingBy(Phone::brand, Collectors.counting()));
Confused? The groupingBy
collector returns a map with the first value passed as the key (Phone::brand
) and the calculation produced by the passed function as the value. In this case, we wanted to count up every phone we saw for that brand, so we used Collectors.counting()
.
The map created by the above looks like this when output:
{Google=2, Apple=2}
Create a count map by summing up some value in each object
To expand on the prior example, let's assume you want to find out how many phones Amazon has at any given time, per brand. We can't just count how many phones there are. Instead, for this example, we have to use the numberAvailable
method from the Phone
class, which tells us how many phones are available on Amazon right now.
Map<String, Integer> numOfPhonesPerBrand = phones.stream().collect(Collectors.groupingBy(Phone::brand, Collectors.summingInt(Phone::numberAvailable)));
In addition to summingInt
, there are collectors called summingLong
and summingDouble
which can add up values from methods returning long or double respectively.
Accumulate into a map of lists
Let's say you have a list of Phone
objects and you want to create a Map<String, List<Phone>>
representing a list of phones by brand. The key is a String
and is the brand, such as "Apple" or "Samsung". The map value is a List<String>
and is just a list of phones such as for Apple it would be ["Apple iPhone 11", "Apple iPhone 11 Pro", etc..].
Map<String, List<Phone>> phoneListByBrand = phones.stream().collect(Collectors.groupingBy(Phone::brand));
Sort Map entries
The Map.Entry
class has a number of its own static methods for use in map operations. You could, for example, sort the map before printing:
phonesByBrand.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(System.out::println);
This will output:
Apple=[Apple iPhone 11, Apple iPhone 11 Pro]
Google=[Google Pixel 4, Google Pixel 4 XL]
Samsung=[Samsung Galaxy Note 10, Samsung Galaxy Fold]
More resources
Check out these resources if you want to learn more about modern Java maps!
Book: Kousen, K. A. (2017). Modern Java recipes: simple solutions to difficult problems in Java 8 and 9.
Top comments (2)
Thanks for the valuable effort.
illustration and explanation more than useful.
You're very welcome!