Java

Chapter 10: The Collections Framework – Mastering Dynamic Data

By Ali Naqi | October 14, 2026

Chapter 10: The Collections Framework – Mastering Dynamic Data



πŸ“š Chapter 10: The Collections Framework – Mastering Dynamic Data

1. The Big Picture: Framework Architecture

The Collections Framework is built on clean interface design. Rather than inventing disparate ways to add or remove elements, Java standardizes these operations under shared interfaces located in the java.util package.


Notice the architecture:

  • At the top sits Iterable, which enables the enhanced for-each loop.
  • Underneath is the core interface: Collection.
  • Collection splits into three primary branches: List, Set, and Queue.
  • Map stands on its own. While technically part of the framework, it models key-value pairs rather than single elements, so it does not inherit from Collection.

2. Generics: Type-Safe Data Containers

Before diving into specific collections, you must understand Generics (indicated by angle brackets <T>).

Prior to Java 5, collections stored raw Object references. You could add an integer to a list of strings, and the compiler would not complainβ€”until your app crashed at runtime with a ClassCastException. Generics allow you to enforce compile-time type safety:

// Raw type (Legacy - Avoid!)
ArrayList rawList = new ArrayList();
rawList.add("Hello");
rawList.add(100); // Compiles, but risks runtime bugs

// Generic type (Modern - Standard practice)
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(100); // Compile-time error: Java stops you immediately!

Wrapper Class Note: Collections can store only reference objects, not primitives. Instead of List<int>, use wrapper classes like List<Integer>, List<Double>, or List<Boolean>. Java handles the conversion automatically via autoboxing and unboxing.


3. The List Family: Ordered and Indexed

A List preserves insertion order and permits duplicates. Every item has an integer index, just like an array.

ArrayList vs. LinkedList

Feature ArrayList LinkedList
Underlying Structure Resizable dynamic array Doubly linked nodes
Random Read (get(i)) $O(1)$ constant time (instant index lookup) $O(n)$ linear time (walks node by node)
Insertion / Deletion at ends Fast ($O(1)$ amortized) Fast ($O(1)$)
Insertion / Deletion in middle Slow ($O(n)$ shifts elements right/left) Fast ($O(1)$ once pointer is at position)
Default Choice? Yes (95% of use cases) Specialized use cases
import java.util.ArrayList;
import java.util.List;

public class ListDemo {
    public static void main(String[] args) {
        // Program to the interface (List), instantiate the class (ArrayList)
        List<String> tasks = new ArrayList<>();

        tasks.add("Review pull request");
        tasks.add("Write unit tests");
        tasks.add("Deploy to staging");

        // Access by index
        System.out.println("First task: " + tasks.get(0));

        // Update by index
        tasks.set(1, "Write integration tests");

        // Remove by value or index
        tasks.remove("Deploy to staging");

        // Check size and existence
        System.out.println("Remaining tasks: " + tasks.size());
        System.out.println("Has review? " + tasks.contains("Review pull request"));
    }
}


4. The Set Family: Uniqueness Guaranteed

A Set rejects duplicates. Attempting to add an element already present will simply return false and leave the set unchanged.

  • HashSet: Backed by a hash table. Offers near-instant operations ($O(1)$ lookup, add, remove), but guarantees no specific ordering.
  • LinkedHashSet: Slightly slower than HashSet, but maintains insertion order.
  • TreeSet: Backed by a Red-Black self-balancing tree. Automatically keeps elements sorted in natural ascending order (or via a custom Comparator), with $O(\log n)$ performance.
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

public class SetDemo {
    public static void main(String[] args) {
        Set<String> uniqueTags = new HashSet<>();
        uniqueTags.add("java");
        uniqueTags.add("backend");
        uniqueTags.add("java"); // Silently ignored

        System.out.println("HashSet (Unordered): " + uniqueTags);

        Set<Integer> sortedScores = new TreeSet<>();
        sortedScores.add(45);
        sortedScores.add(99);
        sortedScores.add(12);

        System.out.println("TreeSet (Sorted): " + sortedScores); // Prints: [12, 45, 99]
    }
}


5. The Map Family: Fast Key-Value Lookups

A Map maps unique keys to specific values. Think of a dictionary: you look up a word (the key) to read its definition (the value). Keys must be unique; values can be duplicated.

  • HashMap: The workhorse. Uses hashing algorithms for $O(1)$ average-time read/write. Unordered.
  • LinkedHashMap: Preserves key insertion order.
  • TreeMap: Sorts keys in ascending order ($O(\log n)$ operations).
import java.util.HashMap;
import java.util.Map;

public class MapDemo {
    public static void main(String[] args) {
        Map<String, Double> productPrices = new HashMap<>();

        // Inserting entries with put()
        productPrices.put("Mechanical Keyboard", 129.99);
        productPrices.put("Wireless Mouse", 59.50);
        productPrices.put("USB-C Hub", 34.00);

        // Overwriting a key
        productPrices.put("Wireless Mouse", 49.99);

        // Safe retrieval
        System.out.println("Keyboard price: $" + productPrices.get("Mechanical Keyboard"));
        System.out.println("Monitor price: $" + productPrices.getOrDefault("Monitor", 0.0));

        // Iterating over entries
        for (Map.Entry<String, Double> entry : productPrices.entrySet()) {
            System.out.println(entry.getKey() + " -> $" + entry.getValue());
        }
    }
}


6. The Queue and Deque: Task Pipelines

When order of processing matters, standard lists aren't explicit enough:

  • Queue (FIFO - First In, First Out): Like standing in line at a grocery store. The first element added is the first one processed. (LinkedList and ArrayDeque implement this).
  • PriorityQueue: Elements are pulled based on their natural priority (or a custom comparator), rather than strict arrival time.
  • Deque (Double-Ended Queue): Elements can be added or removed from both ends. Can serve as both a Queue (FIFO) and a Stack (LIFO - Last In, First Out).
import java.util.ArrayDeque;
import java.util.Deque;

public class QueueDemo {
    public static void main(String[] args) {
        Deque<String> printSpooler = new ArrayDeque<>();

        // Enqueue
        printSpooler.offer("Quarterly_Report.pdf");
        printSpooler.offer("Invoice_1042.pdf");

        // Dequeue (polls from head)
        while (!printSpooler.isEmpty()) {
            System.out.println("Printing: " + printSpooler.poll());
        }
    }
}


7. The Golden Contract: equals() and hashCode()

When storing custom objects in hash-based collections (HashSet, HashMap), failing to override equals() and hashCode() correctly will introduce subtle bugs.

class Student {
    private final int id;
    private final String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student student)) return false;
        return id == student.id;
    }

    @Override
    public int hashCode() {
        return Integer.hashCode(id);
    }
}

The Contract Rules:

  1. If two objects are equal via .equals(), their .hashCode() must return the exact same integer.
  2. If two objects have different hash codes, they are guaranteed not to be equal.
  3. If hash codes match, the objects might be equal; Java calls .equals() to resolve hash collisions.

8. Practical Example: E-Commerce Inventory Ledger

Let's combine lists, sets, and maps into a clean inventory tracker:

import java.util.*;

public class InventoryManager {
    private final Map<String, Integer> stockLevels = new HashMap<>();
    private final Set<String> discontinuedProducts = new HashSet<>();

    public void registerProduct(String sku, int initialStock) {
        stockLevels.put(sku, initialStock);
    }

    public void purchase(String sku, int quantity) {
        if (discontinuedProducts.contains(sku)) {
            System.out.println("Cannot purchase " + sku + ": Product is discontinued.");
            return;
        }

        int current = stockLevels.getOrDefault(sku, 0);
        if (current >= quantity) {
            stockLevels.put(sku, current - quantity);
            System.out.println("Purchased " + quantity + " of " + sku + ". Remaining: " + (current - quantity));
        } else {
            System.out.println("Insufficient stock for SKU: " + sku);
        }
    }

    public void discontinue(String sku) {
        discontinuedProducts.add(sku);
        stockLevels.remove(sku);
    }

    public static void main(String[] args) {
        InventoryManager manager = new InventoryManager();
        manager.registerProduct("SKU-KEYBOARD", 15);
        manager.registerProduct("SKU-MOUSE", 8);

        manager.purchase("SKU-KEYBOARD", 5);
        manager.discontinue("SKU-MOUSE");
        manager.purchase("SKU-MOUSE", 1);
    }
}


9. The Utility Class: java.util.Collections

Just as Arrays provides helpers for raw arrays, Collections provides utility methods for collections:

  • Collections.sort(list): In-place sort.
  • Collections.reverse(list): Inverts item order.
  • Collections.shuffle(list): Randomizes elements.
  • Collections.unmodifiableList(list): Returns a read-only wrapper to safeguard data structures.

πŸ“ Chapter Summary

  • Use ArrayList by default when you need an ordered sequence with rapid index lookups.
  • Use HashSet when elements must be unique and order does not matter.
  • Use TreeSet or TreeMap when values or keys must stay sorted.
  • Use HashMap for key-value pair associations.
  • Always override both **equals() and hashCode()** on custom domain classes placed in hash-based collections.
  • Program to the interface (List<String> items = new ArrayList<>()) to decouple your implementation from specific data structure internals.