Overview of This Lesson

Where lists allow you to create collections that are accessible by a numeric index, Maps are collections that instead allow you to to access elements (values) via a key. The idea of key-value pairs is a popular one in programming, as it makes it easy to associate a value with another value. If numeric 0-based indexes are not useful, perhaps Strings or some other object could be used as an "index" to a specific element. This is where Maps are a good choice for your colleciton.

As with the other Collection types, the Map classes use generic type syntax and only store objects (for both the key and the value). The generic types are usually denoted as <K, V>.

Pre-Requisites

Before reading this lesson, make sure you've gone over the Collections Overview and ArrayList tutorial.

Maps

Maps are similar to Lists in that they contain a list of objects, however where a List's elements are indexed with integer indexes starting at 0, a Map's indexes can be any unique object: it can be an Integer object, String object, or any other object. A Map's elements are key-value pairs, where the key is the unique object that acts as the index, and the value is the object or value associated with each index.

a list of indexed generic values beside a map of generic key-value pairs
A List's elements are organized by index.
A Map contains key-value pairs.

Maps also use generic syntax, so Map<K, V> is used to identify a Map with a generic type K for the key and V for the value.

In the Java Collections framework, the Map<K, V> interface can be implemented on an object that needs to model a map.

A Map is actually a collection of Map.Entry<K, V> objects. Entry is a nested class inside the Map class, which is why we refer to it as Map.Entry. Map.Entry<K, V> models a single key-value pair. This is why we call map elements "entries" rather an "elements" or "items".

So if you had

HashMap<String, Container> containers = new HashMap<String, Container>();

Then each Map.Entry object inside the hash map's entry set is an instance of Map.Entry<String, Container>

a box containing a key-box and a value-box; a map containing several of those boxes
A Map is a collection of Map.Entry objects.
A Map.Entry object consists of a key-value pair.

The Map<K, V> interface contains abstract methods commonly used with most Map objects such as:

Map.Entry<K, V> also has some useful methods:

The Map<K, V> interface is the parent of the SortedMap<K, V> interface, which can be implemented when you want a Map that can be ordered by keys.

The abstract class AbstractMap<K, V> implements the Map<K, V> interface, so it's used as a parent class to concrete Map classes such as TreeMap<K, V> (which implements the SortedMap<K, V> interface) and HashMap<K, V>.

hierarchy of map classes and interfaces
Some of the Map classes and interfaces.

The HashMap Class

A HashMap is a kind of Map that's very efficient when it comes to adding, removing, and searching for map elements.

When you create a HashMap, you must specify a concrete type for both the Key and the Value:

HashMap<Integer, Cat> cats = new HashMap<Integer, Cat>();

In this example, I'm using an Integer object (a boxed int) as the key and a Cat object as the value.

HashMap<String, Container> containers = new HashMap<String, Container>();

Here I'm using a String object as the key and a Container object as the value.

HashMap<Container, Inventory> shipments = new HashMap<Container, Inventory>();

Here I'm using a Container object as the key and an Inventory object as the value.

Whatever you decide to use for your key, that's the entry's index, so that key must be unique. You cannot use the same key more than once in a HashMap.

The default capacity of a HashMap is 16, but you can use the single-parameter int constructor to specify a different capacity:

HashMap<Container, Inventory> shipments = new HashMap<Container, Inventory>(25);

Adding, Retrieving, and Removing Entries

To add entries to a map, we use the put() method:

HashMap<Integer, Cat> cats = new HashMap<>();
cats.put(1, new Cat());

To retrieve entries from a map, we use the get(key) method, where key is the key of the element you want to get:

HashMap<String, Cat> cats = new HashMap<String, Cat>();
cats.put("Arti", new Cat("Arti", "Tabby"));
Cat c = cats.get("Arti");

To remove entries from a map, you can remove by key or by key-value pair. remove(key) removes the object with the specified key and returns it.

HashMap<String, Cat> cats = new HashMap<String, Cat>();
cats.put("Arti", new Cat("Arti", "Tabby"));
Cat c = cats.remove("Arti");

The remove(key, value) searches for a map entry with the specified key. If the entry is not found, it returns false. If the entry is found, it checks to see if the entry has the specified value: if it doesn't, it returns false, but if it does, it removes that entry and returns true.

HashMap<String, Cat> cats = new HashMap<String, Cat>();
cats.put("Arti", new Cat("Arti", "Tabby"));
cats.put("Sophie", new Cat("Sophie", "Calico"));
// removes the first element and returns true
boolean found1 = cats.remove("Arti", new Cat("Arti", "Tabby"));  
// returns false
boolean found2 = cats.remove("Sophie", new Cat("Sophie", "Tabby"));

Note that this remove method uses equals()/hashCode() to determine equality. If the concrete class type does not override equals() and hashCode(), using methods that search the map may not yeild the correct results.

Iterating Maps

Iterating maps is a bit different from iterating lists and sets. Maps don't have iterators, but you can use a for-each loop to iterate a map. However, you're not actually iterating the HashMap itself: HashMap<K, V> does not implement the Iterable interface (so technically, it's not iterable).

This is where we would use the entrySet() method of the HashMap: The entrySet() method actually returns a Set of Map.Entry objects where each Map.Entry models one key-value pair stored in the map. Sets are iterable, as they implement the Iterable interface, so instead of iterating the HashMap, we simply use entrySet() to convert the HashMap into an iterable Set.

So if you had a for-each loop such as:

for (Map.Entry<String, Cat> entry : cats.entrySet()) { 
... 
}

This loop says "For each <String, Cat> entry in the hashmap cats, store the entry in the parameter variable entry for this loop iteration."

The Map.Entry<K, V> class has a getKey() method: this method returns the key as type <K>, in the above example, entry.getKey() would return the String object that's the index for each entry in the map.

Map.Entry<K, V> also has getValue() method: this method returns the value of the entry as type <V>, in the example above, entry.getValue() would return the Cat object stored in an entry of the map.

So you could write a for-each loop that iterates through a hashmap such as:

import java.util.HashMap;
import java.util.Map;
import java.time.LocalDate;
import java.time.Month;

public class Main {
  public static void main (String[] args) {
    HashMap <LocalDate, Double> sales = new HashMap <LocalDate, Double>();
    sales.put(LocalDate.of(2024, Month.JUNE, 24), 2589.92);
    sales.put(LocalDate.of(2024, Month.JUNE, 25), 2244.80);
    sales.put(LocalDate.of(2024, Month.JUNE, 26), 3103.00);
    sales.put(LocalDate.of(2024, Month.JUNE, 27), 3429.42);
    sales.put(LocalDate.of(2024, Month.JUNE, 29), 3476.77);
    sales.put(LocalDate.of(2024, Month.JUNE, 29), 6551.23);

    for (Map.Entry <LocalDate, Double> entry : sales.entrySet()) {

      System.out.printf ("Date: %s    Sales: $%.2f%n", entry.getKey(),
          entry.getValue());
      // note that %s formats date object as a string
    }
  }
}

Here's the same example using an Iterator:

import java.util.HashMap;
import java.util.Map;
import java.time.LocalDate;
import java.time.Month;
import java.util.Iterator;

public class Main {
  public static void main (String[] args) {
    HashMap <LocalDate, Double> sales = new HashMap <LocalDate, Double>();
    sales.put(LocalDate.of(2024, Month.JUNE, 24), 2589.92);
    sales.put(LocalDate.of(2024, Month.JUNE, 25), 2244.80);
    sales.put(LocalDate.of(2024, Month.JUNE, 26), 3103.00);
    sales.put(LocalDate.of(2024, Month.JUNE, 27), 3429.42);
    sales.put(LocalDate.of(2024, Month.JUNE, 29), 3476.77);
    sales.put(LocalDate.of(2024, Month.JUNE, 29), 6551.23);

    Iterator<Map.Entry<LocalDate, Double>> i = sales.entrySet().iterator();
    while (i.hasNext()) {

      Map.Entry<LocalDate, Double> entry = i.next();
      System.out.printf ("Date: %s    Sales: $%.2f%n", entry.getKey(),
          entry.getValue());
      // note that %s formats date object as a string
    }
  }
}

Notice that you get the iterator from the map's entrySet() method, which returns an instance of the Set class. Maps don't have iterators, but Sets do!

Notice also that the parameterized type for the Iterator<E> has to use the type Map.Entry<String, Cat> for the generic type E.