Index

Understanding Maps

Java Collections

7.1 Map Interface and Implementations (HashMap, LinkedHashMap, TreeMap, Hashtable)

In Java Collections, the Map interface represents a collection designed to store key-value pairs, where each unique key maps to exactly one value. Unlike collections like List or Set, which store single elements, a Map stores associations, making it ideal for scenarios where data retrieval by a key is essential — such as dictionaries, caches, and lookup tables.

Overview of the Map Interface

The Map<K, V> interface provides methods to:

Each key in a Map must be unique; if you insert a key that already exists, the previous value is replaced.

Core Implementations and Their Differences

Java provides several popular implementations of the Map interface. Each differs in ordering, thread safety, and performance characteristics.

Implementation Ordering Thread Safety Internal Structure Notes
HashMap No guaranteed order Not synchronized Hash table with buckets (array + linked lists or trees) Fastest general-purpose map. Allows null keys and values.
LinkedHashMap Insertion order Not synchronized Hash table + doubly linked list Maintains predictable iteration order. Slightly slower than HashMap.
TreeMap Sorted by keys (natural or comparator) Not synchronized Red-Black tree (self-balancing binary search tree) Sorted key order. Slower than hash-based maps. No null keys.
Hashtable No guaranteed order Synchronized Hash table Legacy class, synchronized by default, replaced by ConcurrentHashMap for concurrency.

HashMap: Fast Unordered Map

HashMap uses a hash table to store entries. It hashes the key to determine the bucket index where the entry is stored. Each bucket can hold multiple entries if collisions occur, initially stored as a linked list.

Since Java 8, when a bucket’s linked list grows too large (due to many collisions), it converts to a balanced tree (red-black tree) for faster lookup (logarithmic time instead of linear).

Visual analogy: Think of a hash map like a large filing cabinet divided into many drawers (buckets). Each key’s hash code decides which drawer it goes in. Collisions mean multiple files in the same drawer, stored in a linked list or tree.

LinkedHashMap: Ordered by Insertion

LinkedHashMap extends HashMap but maintains a doubly linked list connecting all entries in the order they were inserted. This linked list preserves insertion order during iteration.

TreeMap: Sorted Map

TreeMap stores entries in a red-black tree, a type of self-balancing binary search tree that keeps keys sorted either by their natural ordering (Comparable) or by a custom Comparator.

Visual analogy: Imagine a binary search tree where each node stores a key-value pair. The tree keeps balanced so that retrieval and insertion are efficient and keys are always ordered.

Hashtable: Legacy and Synchronized

Hashtable is an older synchronized map implementation, predating Java Collections Framework. It also uses a hash table but:

For thread-safe, high-performance maps, prefer ConcurrentHashMap.

Summary Table

Feature HashMap LinkedHashMap TreeMap Hashtable
Ordering None Insertion order Sorted order None
Null keys/values allowed Yes (one null key) Yes No (null keys) No
Thread safety No No No Yes (synchronized)
Performance (avg.) O(1) O(1) + linked list overhead O(log n) O(1), but synchronized
Suitable for General use When order matters Sorted key operations Legacy, synchronized

Visual Diagram Suggestion

Map Interface
  ├── HashMap (Hash Table + Linked list / Tree for collisions)
  ├── LinkedHashMap (HashMap + doubly linked list for insertion order)
  ├── TreeMap (Red-Black Tree for sorted keys)
  └── Hashtable (Synchronized Hash Table, legacy)

Understanding the internal workings and characteristics of these core Map implementations enables you to choose the right one for your needs—balancing speed, ordering guarantees, and concurrency requirements.

Index

7.2 Key-Value Pairs and Common Use Cases

At the heart of the Map interface lies the concept of key-value pairs, which is a powerful way to organize and access data efficiently. In a key-value mapping, each unique key acts like an address or identifier that points to a corresponding value. This structure allows you to quickly retrieve, update, or remove data based on its key, rather than searching through entire collections.

What is Key-Value Mapping?

Think of a key-value pair like a dictionary entry in real life: the word is the key, and the definition is the value. You look up a word (the key) to find its meaning (the value). Similarly, in programming, a Map stores these pairs so you can retrieve a value by providing the key — in constant or near-constant time.

Important:

Why Use Maps?

Maps solve many common problems where quick lookup and association between data are required. Here are some practical use cases:

  1. Caching: When you want to store results of expensive computations or database queries, a map can cache these results with the query as the key and the result as the value. This reduces processing time on repeated calls.

  2. Configuration Settings: Applications often read configuration properties as key-value pairs — for example, "timeout" → 5000 or "theme" → "dark". Using a map makes it easy to access and update these settings.

  3. Lookup Tables: If you have a list of user IDs and want to quickly find the associated user details, a map with user ID as key and user info as value is ideal.

  4. Associating Metadata: Maps can store extra information about objects, such as tracking inventory counts ("itemCode" → quantity) or mapping country codes to country names.

Simple Conceptual Example

import java.util.HashMap;
import java.util.Map;

public class KeyValueExample {
    public static void main(String[] args) {
        // Create a Map of employee IDs to names
        Map<Integer, String> employeeMap = new HashMap<>();

        // Add key-value pairs
        employeeMap.put(101, "Alice");
        employeeMap.put(102, "Bob");
        employeeMap.put(103, "Charlie");

        // Retrieve a value using its key
        System.out.println("Employee with ID 102: " + employeeMap.get(102));
        // Output: Employee with ID 102: Bob

        // Attempting to add a duplicate key replaces the old value
        employeeMap.put(102, "Barbara");
        System.out.println("Updated employee with ID 102: " + employeeMap.get(102));
        // Output: Updated employee with ID 102: Barbara

        // Values can be duplicated
        employeeMap.put(104, "Alice");
        System.out.println("Employee with ID 104: " + employeeMap.get(104));
        // Output: Employee with ID 104: Alice
    }
}

Analogy: Library Card Catalog

Imagine a library card catalog where each card is identified by a unique call number (the key). The card contains information about the book (the value). When you want to find a book, you look up its call number, and the card tells you exactly where to find it.

This is precisely how Map structures work: keys uniquely identify the data, making retrieval efficient and intuitive.

Summary

This flexible data structure simplifies many programming tasks by turning complex searches into simple key lookups.

Index

7.3 Runnable Examples: Basic Map usage, updating, and querying

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class MapBasicsExample {
    public static void main(String[] args) {
        // --- Using HashMap ---
        // HashMap stores key-value pairs with no guaranteed order.
        Map<String, Integer> hashMap = new HashMap<>();

        // Adding entries
        hashMap.put("apple", 10);
        hashMap.put("banana", 20);
        hashMap.put("cherry", 30);

        // Updating a value for an existing key ("banana")
        hashMap.put("banana", 25);

        // Removing an entry by key
        hashMap.remove("apple");

        // Querying values
        System.out.println("HashMap contains key 'cherry'? " + hashMap.containsKey("cherry"));
        System.out.println("Value for 'banana': " + hashMap.get("banana"));

        // Iterating over entries (order not guaranteed)
        System.out.println("\nHashMap entries:");
        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }

        // --- Using LinkedHashMap ---
        // LinkedHashMap maintains insertion order during iteration.
        Map<String, Integer> linkedHashMap = new LinkedHashMap<>();

        // Adding entries in a different order
        linkedHashMap.put("apple", 10);
        linkedHashMap.put("banana", 20);
        linkedHashMap.put("cherry", 30);

        // Updating value for "banana"
        linkedHashMap.put("banana", 25);

        // Removing "apple"
        linkedHashMap.remove("apple");

        // Querying values
        System.out.println("\nLinkedHashMap contains key 'cherry'? " + linkedHashMap.containsKey("cherry"));
        System.out.println("Value for 'banana': " + linkedHashMap.get("banana"));

        // Iterating entries (order preserved)
        System.out.println("\nLinkedHashMap entries:");
        for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }
    }
}

Explanation:

Sample Output:

HashMap contains key 'cherry'? true
Value for 'banana': 25

HashMap entries:
banana = 25
cherry = 30

LinkedHashMap contains key 'cherry'? true
Value for 'banana': 25

LinkedHashMap entries:
banana = 25
cherry = 30

This example helps beginners understand basic Map operations and the important distinction between unordered HashMap and insertion-ordered LinkedHashMap. Using these fundamentals, you can choose the right map implementation depending on whether order matters for your use case.

Index