Java Collections Framework - Complete Guide

The Java Collections Framework is a unified architecture for storing and manipulating groups of objects. Instead of creating data structures from scratch, Java provides ready-to-use interfaces and implementations for lists, sets, maps, queues, and other collection types.

The most important part of the Collections Framework is understanding that different data structures solve different problems. An ArrayList is useful when you need an ordered collection with fast index-based access, a HashSet is useful when duplicate values should be eliminated, and a HashMap is useful when data needs to be stored as key-value pairs.

In this guide, we will understand the major collection interfaces, their implementations, common operations, performance characteristics, sorting, iterators, immutable collections, concurrent collections, and practical rules for choosing the right collection.

Why Do We Need Collections?

Arrays are useful when the number of elements is fixed and indexed access is required. However, real applications frequently need dynamic data structures where elements can be added, removed, searched, sorted, or grouped.

For example, an e-commerce application may need a list of products, a set of unique product categories, a map of product IDs to products, and a queue of orders waiting to be processed.

Java
Different application requirements can use different collection types.
List<String> products = new ArrayList<>();
Set<String> categories = new HashSet<>();
Map<Long, String> productNames = new HashMap<>();
Queue<String> orders = new ArrayDeque<>();

Java Collections Hierarchy

The Collection interface represents a group of objects. List, Set, and Queue are the major interfaces derived from Collection. Map is part of the Collections Framework but does not extend Collection because it represents key-value mappings rather than individual elements.

TEXT
Simplified hierarchy of the major Java collection types.
Iterable
└── Collection
    ├── List
    │   ├── ArrayList
    │   └── LinkedList
    │
    ├── Set
    │   ├── HashSet
    │   ├── LinkedHashSet
    │   └── TreeSet
    │
    └── Queue
        ├── PriorityQueue
        └── Deque
            └── ArrayDeque

Map
├── HashMap
├── LinkedHashMap
└── TreeMap

List Interface

List represents an ordered collection. Elements have positions called indexes, duplicate elements are allowed, and elements can generally be accessed using their index.

The most commonly used List implementations are ArrayList and LinkedList. In most general-purpose applications, ArrayList should be the first implementation to consider.

Java
Basic List operations.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> languages = new ArrayList<>();

        languages.add("Java");
        languages.add("Python");
        languages.add("JavaScript");
        languages.add("Java");

        System.out.println(languages);
        System.out.println(languages.get(1));
        System.out.println(languages.size());

        languages.set(1, "Kotlin");
        languages.remove("JavaScript");

        System.out.println(languages);
    }
}

ArrayList

ArrayList is a resizable-array implementation of List. It provides fast random access because elements are stored in an array-like structure internally.

ArrayList is usually the best default choice when you need a general-purpose list. Adding elements at the end is efficient, while inserting or removing elements near the beginning or middle can require shifting other elements.

Java
Using ArrayList for a dynamic collection.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> users = new ArrayList<>();

        users.add("Alice");
        users.add("Bob");
        users.add("Charlie");

        users.add(1, "David");

        System.out.println(users);
        System.out.println("First user: " + users.get(0));
        System.out.println("Size: " + users.size());
    }
}

When Should You Use ArrayList?

Use ArrayList when you frequently access elements by index, mostly add elements to the end, need iteration over a sequence, and do not require special concurrency behavior.

LinkedList

LinkedList implements both List and Deque. It is based on linked nodes rather than a contiguous array. It can efficiently add or remove elements at the ends and can be useful for deque-style operations.

LinkedList is not automatically faster than ArrayList for insertion and removal. If you need to locate an element by index first, traversal can be expensive. For many ordinary list workloads, ArrayList provides better practical performance and memory efficiency.

Java
Using LinkedList as a deque.
import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        LinkedList<String> tasks = new LinkedList<>();

        tasks.addLast("Task 1");
        tasks.addLast("Task 2");
        tasks.addFirst("Urgent Task");

        System.out.println(tasks);

        String first = tasks.removeFirst();
        String last = tasks.removeLast();

        System.out.println("Removed first: " + first);
        System.out.println("Removed last: " + last);
    }
}

Set Interface

Set represents a collection that does not allow duplicate elements. The exact ordering behavior depends on the implementation.

HashSet does not guarantee iteration order, LinkedHashSet preserves insertion order, and TreeSet keeps elements sorted.

Java
Using Set to automatically remove duplicate values.
import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<String> languages = new HashSet<>();

        languages.add("Java");
        languages.add("Python");
        languages.add("Java");
        languages.add("Go");

        System.out.println(languages);
        System.out.println(languages.contains("Java"));
    }
}

HashSet

HashSet uses hashing to provide efficient average-time insertion, removal, and membership checks. It is a good choice when uniqueness matters and element ordering is not part of the requirement.

HashSet uses hashCode and equals to determine whether elements are considered duplicates. Custom classes should therefore implement equals and hashCode consistently when they are stored in hash-based collections.

Java
Using a record with HashSet. Records automatically provide suitable equals and hashCode implementations based on their components.
import java.util.HashSet;
import java.util.Set;

record User(String email) {}

public class Main {
    public static void main(String[] args) {
        Set<User> users = new HashSet<>();

        users.add(new User("alice@example.com"));
        users.add(new User("alice@example.com"));
        users.add(new User("bob@example.com"));

        System.out.println(users);
        System.out.println("Users: " + users.size());
    }
}

LinkedHashSet

LinkedHashSet combines uniqueness with predictable insertion-order iteration. It is useful when duplicate values must be removed but the original order should be preserved.

Java
Removing duplicates while preserving insertion order.
import java.util.LinkedHashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<String> tags = new LinkedHashSet<>();

        tags.add("java");
        tags.add("backend");
        tags.add("java");
        tags.add("api");
        tags.add("backend");

        System.out.println(tags);
    }
}

TreeSet

TreeSet stores unique elements in sorted order. Elements must either implement Comparable or be ordered using a Comparator supplied to the TreeSet.

Java
TreeSet automatically maintains sorted order.
import java.util.Set;
import java.util.TreeSet;

public class Main {
    public static void main(String[] args) {
        Set<Integer> numbers = new TreeSet<>();

        numbers.add(50);
        numbers.add(10);
        numbers.add(30);
        numbers.add(10);
        numbers.add(20);

        System.out.println(numbers);
    }
}

Map Interface

Map stores data as key-value pairs. Every key is unique within a map, while multiple keys can point to equal values.

Maps are commonly used for fast lookup. For example, a user ID can be used as a key to find a user object, or a product code can be used to find product information.

Java
Basic Map operations.
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> users = new HashMap<>();

        users.put(101, "Alice");
        users.put(102, "Bob");
        users.put(103, "Charlie");

        System.out.println(users.get(102));
        System.out.println(users.containsKey(101));

        users.put(102, "Robert");
        users.remove(103);

        System.out.println(users);
    }
}

HashMap

HashMap is the most commonly used general-purpose Map implementation. It provides efficient average-time lookup, insertion, and removal based on hashing.

HashMap does not guarantee iteration order. If your application depends on insertion order or sorted keys, choose a different implementation.

Java
Counting values with HashMap.
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        String[] words = {"java", "python", "java", "go", "java"};

        Map<String, Integer> counts = new HashMap<>();

        for (String word : words) {
            counts.merge(word, 1, Integer::sum);
        }

        System.out.println(counts);
    }
}

Important HashMap Methods

Modern Java provides several methods that make map-based operations concise and safe. getOrDefault is useful when a key may not exist, putIfAbsent inserts only when a key is missing, and computeIfAbsent can lazily create a value.

Java
Common Map methods for practical application logic.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, List<String>> groups = new HashMap<>();

        groups.computeIfAbsent("backend", key -> new ArrayList<>())
              .add("Java");

        groups.computeIfAbsent("backend", key -> new ArrayList<>())
              .add("Spring");

        groups.putIfAbsent("frontend", new ArrayList<>());

        System.out.println(groups);
    }
}

LinkedHashMap

LinkedHashMap maintains a predictable iteration order. By default, that order is insertion order. It can also be configured for access-order behavior, which is useful for certain cache implementations.

Java
Preserving insertion order with LinkedHashMap.
import java.util.LinkedHashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = new LinkedHashMap<>();

        scores.put("Alice", 90);
        scores.put("Bob", 85);
        scores.put("Charlie", 95);

        scores.forEach((name, score) ->
            System.out.println(name + " -> " + score)
        );
    }
}

TreeMap

TreeMap stores key-value pairs in sorted key order. It is useful when applications need ordered navigation or range-based operations in addition to map lookup.

Java
TreeMap automatically sorts entries by key.
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<Integer, String> users = new TreeMap<>();

        users.put(30, "Charlie");
        users.put(10, "Alice");
        users.put(20, "Bob");

        System.out.println(users);
        System.out.println("First key: " + ((TreeMap<Integer, String>) users).firstKey());
        System.out.println("Last key: " + ((TreeMap<Integer, String>) users).lastKey());
    }
}

Queue Interface

Queue represents a collection designed for holding elements before they are processed. A typical queue follows FIFO behavior, meaning the first element inserted is normally the first element removed.

The most common Queue methods are offer, poll, and peek. These methods are preferable when you want operations that return special values instead of throwing exceptions when the operation cannot be completed.

Java
Basic FIFO queue operations.
import java.util.ArrayDeque;
import java.util.Queue;

public class Main {
    public static void main(String[] args) {
        Queue<String> queue = new ArrayDeque<>();

        queue.offer("Order-101");
        queue.offer("Order-102");
        queue.offer("Order-103");

        System.out.println("Next: " + queue.peek());
        System.out.println("Processing: " + queue.poll());
        System.out.println("Remaining: " + queue);
    }
}

PriorityQueue

PriorityQueue does not behave like a normal FIFO queue. Elements are ordered according to their natural ordering or a supplied Comparator, and poll returns the element at the head of that priority ordering.

Java
Processing tasks according to priority.
import java.util.PriorityQueue;
import java.util.Queue;

record Task(String name, int priority) {}

public class Main {
    public static void main(String[] args) {
        Queue<Task> tasks = new PriorityQueue<>(
            (a, b) -> Integer.compare(a.priority(), b.priority())
        );

        tasks.offer(new Task("Normal task", 3));
        tasks.offer(new Task("Critical task", 1));
        tasks.offer(new Task("Important task", 2));

        while (!tasks.isEmpty()) {
            Task task = tasks.poll();
            System.out.println(task.name());
        }
    }
}

Deque and ArrayDeque

Deque stands for double-ended queue. It supports insertion and removal at both the beginning and the end. ArrayDeque is usually preferred over Stack for stack-like behavior in modern Java code.

Java
Using ArrayDeque as both a queue and a stack.
import java.util.ArrayDeque;
import java.util.Deque;

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

        deque.addLast("A");
        deque.addLast("B");
        deque.addLast("C");

        System.out.println(deque.removeFirst());

        deque.addFirst("Start");
        System.out.println(deque);

        System.out.println("Stack pop: " + deque.removeLast());
    }
}

Iterator

Iterator provides a standard way to traverse collections. One important advantage is that Iterator provides a remove method that can safely remove the current element during iteration.

Java
Removing elements safely during iteration.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        numbers.add(10);
        numbers.add(20);
        numbers.add(30);
        numbers.add(40);

        Iterator<Integer> iterator = numbers.iterator();

        while (iterator.hasNext()) {
            int number = iterator.next();

            if (number % 20 == 0) {
                iterator.remove();
            }
        }

        System.out.println(numbers);
    }
}

Sorting Collections

Lists can be sorted using List.sort or Collections.sort. Comparator allows you to define custom sorting rules without changing the underlying class.

Java
Sorting strings by length and then alphabetically.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Alexander");
        names.add("Bob");
        names.add("Chris");
        names.add("Ann");

        names.sort(
            Comparator.comparingInt(String::length)
                      .thenComparing(String::compareTo)
        );

        System.out.println(names);
    }
}

Sorting Custom Objects

When working with domain objects, Comparator can define different sorting strategies. The same list can therefore be sorted by price, name, date, priority, or any other property.

Java
Sorting products using multiple fields.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

record Product(String name, double price) {}

public class Main {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();

        products.add(new Product("Laptop", 1200));
        products.add(new Product("Mouse", 30));
        products.add(new Product("Keyboard", 80));

        products.sort(Comparator.comparingDouble(Product::price));

        products.forEach(product ->
            System.out.println(product.name() + " - " + product.price())
        );
    }
}

equals and hashCode

Hash-based collections such as HashMap and HashSet depend on equals and hashCode. If two objects are logically equal, their hashCode values must also be equal.

When creating custom classes used as keys in HashMap or elements in HashSet, implement equals and hashCode consistently. Java records are convenient because they automatically generate these methods from their components.

Java
A custom class with equals and hashCode based on the identifier.
import java.util.Objects;

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

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof User other)) return false;
        return id == other.id;
    }

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

    @Override
    public String toString() {
        return id + ": " + name;
    }
}

Immutable and Unmodifiable Collections

Java provides factory methods such as List.of, Set.of, and Map.of for creating unmodifiable collections. These are useful when a collection should not be changed after creation.

These factory methods reject null elements or keys and do not permit structural modification. If you need a mutable collection initialized from existing data, create a mutable copy such as new ArrayList<>(list).

Java
Creating unmodifiable collections and a mutable copy.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        List<String> languages = List.of("Java", "Python", "Go");
        Set<Integer> numbers = Set.of(10, 20, 30);
        Map<String, Integer> scores = Map.of(
            "Alice", 90,
            "Bob", 85
        );

        List<String> mutableLanguages = new ArrayList<>(languages);
        mutableLanguages.add("Kotlin");

        System.out.println(languages);
        System.out.println(numbers);
        System.out.println(scores);
        System.out.println(mutableLanguages);
    }
}

Collections Utility Class

The java.util.Collections class contains utility methods for working with collections. Common operations include sorting, reversing, shuffling, finding minimum and maximum values, and performing binary searches on sorted lists.

Java
Using common Collections utility methods.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        numbers.add(40);
        numbers.add(10);
        numbers.add(30);
        numbers.add(20);

        Collections.sort(numbers);
        System.out.println("Sorted: " + numbers);

        System.out.println("Min: " + Collections.min(numbers));
        System.out.println("Max: " + Collections.max(numbers));

        int index = Collections.binarySearch(numbers, 30);
        System.out.println("Index of 30: " + index);

        Collections.reverse(numbers);
        System.out.println("Reversed: " + numbers);
    }
}

Concurrent Collections

Normal collections such as ArrayList and HashMap are not designed for unsynchronized concurrent modification by multiple threads. The java.util.concurrent package provides specialized collections for concurrent applications.

Important concurrent collections include ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue, and several BlockingQueue implementations.

Java
Using ConcurrentHashMap for concurrent map operations.
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class Main {
    public static void main(String[] args) {
        ConcurrentMap<String, Integer> counts = new ConcurrentHashMap<>();

        counts.put("Java", 10);
        counts.merge("Java", 5, Integer::sum);
        counts.putIfAbsent("Python", 3);

        System.out.println(counts);
    }
}

CopyOnWriteArrayList

CopyOnWriteArrayList is a thread-safe List implementation designed for workloads where reads are frequent and modifications are relatively rare. Instead of modifying the existing backing array, a write operation creates a new copy.

This design makes iteration safe without requiring external synchronization, but frequent writes can be expensive because each modification may copy the underlying array.

Java
Basic usage of CopyOnWriteArrayList.
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {
        List<String> listeners = new CopyOnWriteArrayList<>();

        listeners.add("Listener-A");
        listeners.add("Listener-B");

        for (String listener : listeners) {
            System.out.println(listener);
        }
    }
}

BlockingQueue

BlockingQueue is designed for producer-consumer systems. A producer can wait when the queue is full, while a consumer can wait when the queue is empty.

Java
A simple producer-consumer example using ArrayBlockingQueue.
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);

        Thread producer = new Thread(() -> {
            try {
                queue.put("Task 1");
                queue.put("Task 2");
                queue.put("Task 3");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    String task = queue.take();
                    System.out.println("Processing: " + task);

                    if (task.equals("Task 3")) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();
    }
}

Collection Performance

Understanding approximate operation complexity helps when choosing a collection. Big-O describes how an operation scales with the number of elements, but real-world performance also depends on memory locality, object allocation, workload, and implementation details.

TEXT
Common expected operation characteristics.
ArrayList
- get(index): O(1)
- set(index): O(1)
- add at end: amortized O(1)
- search: O(n)
- insert/remove at middle: O(n)

HashSet
- add: average O(1)
- contains: average O(1)
- remove: average O(1)

TreeSet
- add: O(log n)
- contains: O(log n)
- remove: O(log n)

HashMap
- put: average O(1)
- get: average O(1)
- containsKey: average O(1)

TreeMap
- put: O(log n)
- get: O(log n)
- remove: O(log n)

ArrayDeque
- add/remove at either end: O(1) amortized

ArrayList vs LinkedList

ArrayList provides fast index-based access and is generally the better default for ordinary list workloads. LinkedList can be useful when its deque operations match the problem, but it should not be selected simply because insertion or deletion is theoretically efficient.

TEXT
High-level comparison between the two List implementations.
ArrayList
- Backed by a resizable array
- Fast random access
- Efficient iteration
- Good general-purpose List
- Insert/remove in middle may require shifting elements

LinkedList
- Node-based structure
- Implements List and Deque
- Efficient operations at known ends
- Slower random access
- Higher memory overhead per element

HashMap vs TreeMap

HashMap is generally preferred when you need key-based lookup without ordering requirements. TreeMap is appropriate when keys must remain sorted or when you need navigable and range-based operations.

TEXT
Choosing between HashMap and TreeMap.
HashMap
- Hash-based lookup
- Average O(1) basic operations
- No guaranteed iteration order
- General-purpose Map

TreeMap
- Sorted keys
- O(log n) basic operations
- Supports navigable operations
- Useful for ordered and range-based data

How to Choose the Right Collection

Start by identifying the behavior your application needs instead of choosing an implementation first.

TEXT
A practical decision guide for common collection requirements.
Need ordered elements with index access?
-> List / ArrayList

Need unique elements?
-> Set / HashSet

Need unique elements and insertion order?
-> LinkedHashSet

Need unique sorted elements?
-> TreeSet

Need key-value lookup?
-> HashMap

Need key-value lookup with insertion order?
-> LinkedHashMap

Need sorted keys?
-> TreeMap

Need FIFO processing?
-> Queue / ArrayDeque

Need operations at both ends?
-> Deque / ArrayDeque

Need priority-based processing?
-> PriorityQueue

Need concurrent key-value access?
-> ConcurrentHashMap

Need producer-consumer communication?
-> BlockingQueue

Generics and Type Safety

Generics allow collections to specify the type of elements they contain. This provides compile-time type checking and eliminates many explicit casts.

Java
Using generics to create a type-safe collection.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Alice");
        names.add("Bob");

        String firstName = names.get(0);

        System.out.println(firstName);
    }
}

Prefer List, Map, or Set over raw collection types such as List, Map, or Set. Generic types make APIs easier to understand and prevent many runtime type errors.

Java Collections Best Practices

Program to interfaces rather than concrete implementations. Declare variables using List, Set, Map, Queue, or Deque when the implementation details are not part of the API requirement.

Choose a collection based on required behavior rather than assumptions about speed. Consider ordering, uniqueness, lookup patterns, insertion patterns, memory usage, and concurrency requirements.

Use generics consistently, avoid raw collections, and prefer immutable collections when data should not be modified.

Do not rely on the iteration order of HashMap or HashSet. If order matters, explicitly choose LinkedHashMap, LinkedHashSet, TreeMap, or TreeSet depending on the required ordering semantics.

For concurrent applications, use collections from java.util.concurrent when their semantics match the problem instead of manually synchronizing ordinary collections without understanding the required concurrency behavior.

Java
Program against the List interface while choosing ArrayList as the implementation.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> users = new ArrayList<>();

        users.add("Alice");
        users.add("Bob");

        for (String user : users) {
            System.out.println(user);
        }
    }
}

Common Collection Mistakes

One common mistake is using a List when the application actually requires uniqueness. A Set can express that requirement directly and avoid manually checking for duplicates.

Another common mistake is expecting HashMap or HashSet to preserve insertion order. Their iteration order should not be treated as an application contract.

Removing elements directly from a collection inside an enhanced for loop can cause ConcurrentModificationException for fail-fast iterators. Use Iterator.remove or an appropriate collection operation instead.

Java
Using removeIf to safely remove matching elements.
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        numbers.add(10);
        numbers.add(15);
        numbers.add(20);
        numbers.add(25);

        numbers.removeIf(number -> number % 2 != 0);

        System.out.println(numbers);
    }
}

Real-World Example: Product Catalog

A product catalog can use multiple collection types together. A List can maintain products in display order, a Map can provide fast lookup by product ID, and a Set can maintain unique categories.

Java
Combining List, Map, and Set in a simple product catalog.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

record Product(long id, String name, String category) {}

public class Main {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();
        Map<Long, Product> productsById = new HashMap<>();
        Set<String> categories = new HashSet<>();

        Product laptop = new Product(101, "Laptop", "Electronics");
        Product keyboard = new Product(102, "Keyboard", "Electronics");
        Product chair = new Product(103, "Office Chair", "Furniture");

        products.add(laptop);
        products.add(keyboard);
        products.add(chair);

        for (Product product : products) {
            productsById.put(product.id(), product);
            categories.add(product.category());
        }

        System.out.println("Product: " + productsById.get(102));
        System.out.println("Categories: " + categories);
    }
}

Java Collections Quick Reference

TEXT
Quick reference for commonly used Java collection implementations.
ArrayList       -> Ordered list, duplicates allowed, fast index access
LinkedList      -> List + Deque behavior
HashSet         -> Unique elements, no guaranteed order
LinkedHashSet   -> Unique elements, insertion order
TreeSet         -> Unique elements, sorted order
HashMap         -> Key-value lookup, no guaranteed order
LinkedHashMap   -> Key-value lookup, insertion order
TreeMap         -> Key-value lookup, sorted keys
ArrayDeque      -> Efficient operations at both ends
PriorityQueue   -> Priority-based processing
ConcurrentHashMap -> Concurrent key-value operations
BlockingQueue   -> Producer-consumer communication

Conclusion

The Java Collections Framework provides a complete set of reusable data structures for everyday application development. The key to using collections effectively is not memorizing every class, but understanding the behavior each interface and implementation provides.

Use List when order and duplicates matter, Set when uniqueness matters, Map when key-value lookup is required, Queue when elements need to be processed in sequence, and Deque when both ends need to be accessed.

Once the basic interfaces are understood, choose between implementations such as ArrayList, HashSet, LinkedHashSet, TreeSet, HashMap, LinkedHashMap, TreeMap, ArrayDeque, and PriorityQueue based on ordering, performance, memory, and concurrency requirements.