A Map is a data structure for storing key value pairs. It is similar to dictionaries in other programming languages. The Map interface defines the operations you can make with a map.
Java has a number of different Map implementations. HashMap is a commonly used one.
// Make an instance
Map<String, Integer> fruitPrices = new HashMap<>();
When defining a `Map` variable, it is recommended to define the variable as a `Map` type rather than the specific type, as in the above example.
This practice makes it easy to change the `Map` implementation later.
HashMap
also has a copy constructor.
// Make a copy of a map
Map<String, Integer> copy = new HashMap<>(fruitPrices);
Add entries to the map using put.
fruitPrices.put("apple", 100);
fruitPrices.put("pear", 80);
// => { "apple" => 100, "pear" => 80 }
Only one value can be associated with each key.
Calling put
with the same key will update the key's value.
fruitPrices.put("pear", 40);
// => { "apple" => 100, "pear" => 40 }
Use get to get the value for a key.
fruitPrices.get("apple"); // => 100
Use containsKey to see if the map contains a particular key.
fruitPrices.containsKey("apple"); // => true
fruitPrices.containsKey("orange"); // => false
Remove entries with remove.
fruitPrices.put("plum", 90); // Add plum to map
fruitPrices.remove("plum"); // Removes plum from map
The size method returns the number of entries.
fruitPrices.size(); // Returns 2
You can use the [keys] or [values] methods to obtain the keys or the values in a Map as a Set or collection respectively.
fruitPrices.keys(); // Returns "apple" and "pear" in a set
fruitPrices.values(); // Returns 100 and 80, in a Collection
HashMaps uses the object's hashCode and equals method to work out where to store and how to retrieve the values for a key. For this reason, it is important that their return values do not change between storing and getting them, otherwise the HashMap may not be able to find the value.
For example, lets say we have the following class that will be used as the key to a map:
public class Stock {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (Objects.equals(Stock.class, obj.getClass()) && obj instanceof Stock other) {
return Objects.equals(name, other.name);
}
return false;
}
}
The hashCode
and equals
depend on the name
field, which can be changed via setName
.
Altering the hashCode
can produce surprising results:
Stock stock = new Stock();
stock.setName("Beanies");
Map<Stock, Integer> stockCount = new HashMap<>();
stockCount.put(stock, 80);
stockCount.get(stock); // Returns 80
Stock other = new Stock();
other.setName("Beanies");
stockCount.get(other); // Returns 80 because "other" and "stock" are equal
stock.setName("Choccies");
stockCount.get(stock); // Returns null because hashCode value has changed
stockCount.get(other); // Also returns null because "other" and "stock" are not equal
stock.setName("Beanies");
stockCount.get(stock); // HashCode restored, so returns 80 again
stockCount.get(other); // Also returns 80 again because "other" and "stock" are back to equal
Another common way to create maps is to use Map.of or Map.ofEntries.
// Using Map.of
Map<String, Integer> temperatures = Map.of("Mon", 30, "Tue", 28, "Wed", 32);
// or using Map.ofEntries
Map<String, Integer> temperatures2 = Map.ofEntries(Map.entry("Mon", 30, "Tue", 28, "Wed", 32));
Unlike HashMap
, they populate the map upfront and become read-only once created.
Map.copyOf makes a read-only copy of a map.
Map<String, Integer> readOnlyFruitPrices = Map.copyOf(fruitPrices);
Calling methods like put
, remove
or clear
results in an UnsupportedOperationException
.