Java Programming Collections Framework
Data Structures

Java Collections Framework - Complete Tutorial

Master Java Collections: Learn List, Set, Map, Queue interfaces with ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, PriorityQueue implementations, iterators, comparators and best practices.

List Interface

ArrayList, LinkedList

Set Interface

HashSet, TreeSet

Map Interface

HashMap, TreeMap

Queue Interface

PriorityQueue

1. Introduction to Java Collections Framework

The Java Collections Framework (JCF) is a unified architecture for representing and manipulating collections. It provides ready-to-use data structures and algorithms to handle groups of objects efficiently.

Why Collections Framework?
  • Reduces Programming Effort: Ready-to-use data structures
  • Increases Performance: High-performance implementations
  • Provides Interoperability: Standard interface for collections
  • Reduces Learning Effort: Consistent API across types
  • Software Reuse: Can be easily extended
  • Type Safety: Generics provide compile-time type checking
Key Interfaces
  • Collection: Root interface
  • List: Ordered, indexed, allows duplicates
  • Set: No duplicates, unordered (except TreeSet)
  • Map: Key-value pairs, no duplicate keys
  • Queue: FIFO or priority-based ordering
  • Deque: Double-ended queue
  • SortedSet & SortedMap: Sorted collections

Collections Framework Hierarchy

JCF follows a well-defined hierarchy: Iterable → Collection → (List, Set, Queue) & Map → Concrete Implementations. Understanding this hierarchy is key to mastering collections.

Java Collections Framework Hierarchy
                        Iterable (Interface)
                            ↓
                        Collection (Interface)
                            ├── List (Interface)
                            │     ├── ArrayList (Class)
                            │     ├── LinkedList (Class)
                            │     └── Vector (Class)
                            │           └── Stack (Class)
                            │
                            ├── Set (Interface)
                            │     ├── HashSet (Class)
                            │     │      └── LinkedHashSet (Class)
                            │     └── SortedSet (Interface)
                            │           └── TreeSet (Class)
                            │
                            └── Queue (Interface)
                                  ├── PriorityQueue (Class)
                                  └── Deque (Interface)
                                        └── ArrayDeque (Class)

                        Map (Interface) - NOT part of Collection hierarchy
                            ├── HashMap (Class)
                            │      └── LinkedHashMap (Class)
                            ├── Hashtable (Class)
                            └── SortedMap (Interface)
                                  └── TreeMap (Class)
                    
List
Ordered Collection

Index-based access, allows duplicates

Set
Unique Elements

No duplicates, unordered (except TreeSet)

Map
Key-Value Pairs

Unique keys, values can be duplicated

Queue
FIFO/Priority

Order processing, priority-based

2. List Interface and Implementations

The List interface represents an ordered collection (sequence). It allows positional access, duplicate elements, and null values. Main implementations: ArrayList, LinkedList, and Vector.

ListExamples.java
import java.util.*;

public class ListExamples {
    public static void main(String[] args) {
        System.out.println("=== ArrayList Examples ===");
        
        // ArrayList creation
        List arrayList = new ArrayList<>();
        
        // Adding elements
        arrayList.add("Apple");
        arrayList.add("Banana");
        arrayList.add("Cherry");
        arrayList.add("Date");
        arrayList.add("Elderberry");
        
        System.out.println("ArrayList: " + arrayList);
        System.out.println("Size: " + arrayList.size());
        System.out.println("Is empty: " + arrayList.isEmpty());
        
        // Accessing elements
        System.out.println("\n=== Access Operations ===");
        System.out.println("First element: " + arrayList.get(0));
        System.out.println("Last element: " + arrayList.get(arrayList.size() - 1));
        System.out.println("Index of 'Cherry': " + arrayList.indexOf("Cherry"));
        System.out.println("Contains 'Banana': " + arrayList.contains("Banana"));
        
        // Modifying elements
        System.out.println("\n=== Modification Operations ===");
        arrayList.set(2, "Coconut");  // Replace at index 2
        System.out.println("After set(2, 'Coconut'): " + arrayList);
        
        arrayList.add(3, "Dragonfruit");  // Insert at index 3
        System.out.println("After add(3, 'Dragonfruit'): " + arrayList);
        
        arrayList.remove("Banana");  // Remove by object
        System.out.println("After remove('Banana'): " + arrayList);
        
        arrayList.remove(0);  // Remove by index
        System.out.println("After remove(0): " + arrayList);
        
        // Sublist
        System.out.println("\n=== Sublist Operations ===");
        List subList = arrayList.subList(1, 3);
        System.out.println("Sublist(1, 3): " + subList);
        
        // Bulk operations
        System.out.println("\n=== Bulk Operations ===");
        List fruitsToAdd = Arrays.asList("Fig", "Grape", "Honeydew");
        arrayList.addAll(fruitsToAdd);
        System.out.println("After addAll: " + arrayList);
        
        arrayList.removeAll(Arrays.asList("Coconut", "Dragonfruit"));
        System.out.println("After removeAll: " + arrayList);
        
        arrayList.retainAll(Arrays.asList("Date", "Elderberry", "Fig"));
        System.out.println("After retainAll: " + arrayList);
        
        // Iteration
        System.out.println("\n=== Iteration Methods ===");
        System.out.print("Using for-each: ");
        for (String fruit : arrayList) {
            System.out.print(fruit + " ");
        }
        
        System.out.print("\nUsing iterator: ");
        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
        
        System.out.print("\nUsing listIterator (backwards): ");
        ListIterator listIterator = arrayList.listIterator(arrayList.size());
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous() + " ");
        }
        
        System.out.print("\nUsing forEach (Java 8+): ");
        arrayList.forEach(fruit -> System.out.print(fruit + " "));
        
        System.out.println("\n\n=== LinkedList Examples ===");
        List linkedList = new LinkedList<>();
        linkedList.add("First");
        linkedList.add("Second");
        linkedList.add("Third");
        
        System.out.println("LinkedList: " + linkedList);
        
        // LinkedList specific operations (when cast to LinkedList)
        LinkedList ll = (LinkedList) linkedList;
        ll.addFirst("Zero");
        ll.addLast("Fourth");
        System.out.println("After addFirst and addLast: " + ll);
        System.out.println("First element: " + ll.getFirst());
        System.out.println("Last element: " + ll.getLast());
        System.out.println("Removed first: " + ll.removeFirst());
        System.out.println("Removed last: " + ll.removeLast());
        System.out.println("Final LinkedList: " + ll);
        
        // Sorting
        System.out.println("\n=== Sorting Lists ===");
        List numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3));
        System.out.println("Original: " + numbers);
        
        Collections.sort(numbers);
        System.out.println("Sorted ascending: " + numbers);
        
        Collections.sort(numbers, Collections.reverseOrder());
        System.out.println("Sorted descending: " + numbers);
        
        // Custom sorting with Comparator
        List names = new ArrayList<>(Arrays.asList("John", "Alice", "Bob", "Charlie"));
        System.out.println("\nNames original: " + names);
        Collections.sort(names);
        System.out.println("Names sorted naturally: " + names);
        
        Collections.sort(names, (a, b) -> a.length() - b.length());
        System.out.println("Names sorted by length: " + names);
        
        // List to array and vice versa
        System.out.println("\n=== Array Conversion ===");
        String[] array = arrayList.toArray(new String[0]);
        System.out.println("Array from list: " + Arrays.toString(array));
        
        List listFromArray = Arrays.asList(array);
        System.out.println("List from array: " + listFromArray);
        
        // Clear list
        arrayList.clear();
        System.out.println("\nAfter clear(), size: " + arrayList.size());
    }
}
Implementation When to Use Key Features Time Complexity
ArrayList Frequent read operations, random access Resizable array, fast random access Get/Set: O(1)
Add/Remove at end: O(1)
Add/Remove at index: O(n)
LinkedList Frequent add/remove operations Doubly-linked list, queue operations Get/Set: O(n)
Add/Remove at ends: O(1)
Add/Remove at index: O(n)
Vector Thread-safe operations needed Synchronized, legacy class Similar to ArrayList but slower due to synchronization
ArrayList Methods Reference
add(E e) Appends element
add(int index, E e) Inserts at position
get(int index) Returns element at index
set(int index, E e) Replaces element at index
remove(int index) Removes element at index
remove(Object o) Removes first occurrence
indexOf(Object o) Returns first index
lastIndexOf(Object o) Returns last index
subList(int from, int to) Returns sublist view
LinkedList Methods Reference
addFirst(E e) Inserts at beginning
addLast(E e) Appends to end
getFirst() Returns first element
getLast() Returns last element
removeFirst() Removes first element
removeLast() Removes last element
offer(E e) Adds as last (queue)
poll() Removes first (queue)
peek() Retrieves first (queue)
ArrayList vs LinkedList - When to Use Which?
  • Use ArrayList when: More get/set operations, random access needed, less insertions/deletions
  • Use LinkedList when: More insertions/deletions, frequently adding/removing at ends, implementing stack/queue
  • Memory: ArrayList uses less memory per element
  • Performance: ArrayList better for iteration, LinkedList better for manipulation
  • Rule of thumb: Default to ArrayList unless you specifically need LinkedList features

3. Set Interface and Implementations

The Set interface represents a collection that cannot contain duplicate elements. It models the mathematical set abstraction. Main implementations: HashSet, LinkedHashSet, and TreeSet.

SetExamples.java
import java.util.*;

public class SetExamples {
    public static void main(String[] args) {
        System.out.println("=== HashSet Examples ===");
        
        // HashSet - unordered, uses HashMap internally
        Set hashSet = new HashSet<>();
        
        // Adding elements (duplicates ignored)
        hashSet.add("Apple");
        hashSet.add("Banana");
        hashSet.add("Cherry");
        hashSet.add("Apple");  // Duplicate - ignored
        hashSet.add("Date");
        hashSet.add(null);     // Allows null
        
        System.out.println("HashSet: " + hashSet);
        System.out.println("Size: " + hashSet.size());
        System.out.println("Is empty: " + hashSet.isEmpty());
        System.out.println("Contains 'Banana': " + hashSet.contains("Banana"));
        
        // Removing elements
        hashSet.remove("Date");
        System.out.println("After remove('Date'): " + hashSet);
        
        // Bulk operations
        Set otherFruits = new HashSet<>(Arrays.asList("Fig", "Grape", "Apple"));
        hashSet.addAll(otherFruits);
        System.out.println("After addAll: " + hashSet);
        
        hashSet.retainAll(Arrays.asList("Apple", "Banana", "Fig"));
        System.out.println("After retainAll: " + hashSet);
        
        hashSet.removeAll(Arrays.asList("Fig"));
        System.out.println("After removeAll: " + hashSet);
        
        // Iteration (order not guaranteed)
        System.out.print("\nIteration (unordered): ");
        for (String fruit : hashSet) {
            System.out.print(fruit + " ");
        }
        
        // Clear set
        hashSet.clear();
        System.out.println("\nAfter clear, size: " + hashSet.size());
        
        System.out.println("\n=== LinkedHashSet Examples ===");
        
        // LinkedHashSet - maintains insertion order
        Set linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("Zebra");
        linkedHashSet.add("Apple");
        linkedHashSet.add("Monkey");
        linkedHashSet.add("Banana");
        
        System.out.println("LinkedHashSet (insertion order): " + linkedHashSet);
        
        System.out.print("Iteration (insertion order): ");
        linkedHashSet.forEach(item -> System.out.print(item + " "));
        
        System.out.println("\n\n=== TreeSet Examples ===");
        
        // TreeSet - sorted order (natural or comparator)
        Set treeSet = new TreeSet<>();
        treeSet.add("Orange");
        treeSet.add("Apple");
        treeSet.add("Banana");
        treeSet.add("Cherry");
        treeSet.add("Apple");  // Duplicate ignored
        
        System.out.println("TreeSet (sorted naturally): " + treeSet);
        
        // TreeSet with custom comparator
        Set reverseTreeSet = new TreeSet<>(Collections.reverseOrder());
        reverseTreeSet.addAll(Arrays.asList("Orange", "Apple", "Banana", "Cherry"));
        System.out.println("TreeSet (reverse order): " + reverseTreeSet);
        
        // TreeSet operations
        System.out.println("\n=== TreeSet Specific Operations ===");
        TreeSet numbers = new TreeSet<>(Arrays.asList(10, 5, 15, 3, 7, 12, 18));
        System.out.println("Numbers TreeSet: " + numbers);
        System.out.println("First (lowest): " + numbers.first());
        System.out.println("Last (highest): " + numbers.last());
        System.out.println("Lower than 7: " + numbers.lower(7));      // greatest < 7
        System.out.println("Higher than 7: " + numbers.higher(7));    // least > 7
        System.out.println("Floor of 8: " + numbers.floor(8));        // greatest ≤ 8
        System.out.println("Ceiling of 8: " + numbers.ceiling(8));    // least ≥ 8
        
        // Subset operations
        System.out.println("\n=== Subset Operations ===");
        System.out.println("HeadSet (<10): " + numbers.headSet(10));          // elements < 10
        System.out.println("TailSet (≥10): " + numbers.tailSet(10));          // elements ≥ 10
        System.out.println("SubSet (5 to 15): " + numbers.subSet(5, 15));     // 5 ≤ elements < 15
        System.out.println("SubSet inclusive (5 to 15): " + numbers.subSet(5, true, 15, true)); // 5 ≤ elements ≤ 15
        
        // Poll operations (remove and return)
        System.out.println("\n=== Poll Operations ===");
        System.out.println("Poll first (remove lowest): " + numbers.pollFirst());
        System.out.println("Poll last (remove highest): " + numbers.pollLast());
        System.out.println("After polling: " + numbers);
        
        // Set operations - Union, Intersection, Difference
        System.out.println("\n=== Set Operations (Mathematical) ===");
        Set setA = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
        Set setB = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
        
        System.out.println("Set A: " + setA);
        System.out.println("Set B: " + setB);
        
        // Union (A ∪ B)
        Set union = new HashSet<>(setA);
        union.addAll(setB);
        System.out.println("Union (A ∪ B): " + union);
        
        // Intersection (A ∩ B)
        Set intersection = new HashSet<>(setA);
        intersection.retainAll(setB);
        System.out.println("Intersection (A ∩ B): " + intersection);
        
        // Difference (A - B)
        Set difference = new HashSet<>(setA);
        difference.removeAll(setB);
        System.out.println("Difference (A - B): " + difference);
        
        // Symmetric Difference (A Δ B) = (A ∪ B) - (A ∩ B)
        Set symmetricDiff = new HashSet<>(union);
        symmetricDiff.removeAll(intersection);
        System.out.println("Symmetric Difference (A Δ B): " + symmetricDiff);
        
        // Check subset
        System.out.println("Is {4,5} subset of A? " + setA.containsAll(Arrays.asList(4, 5)));
        System.out.println("Is A subset of union? " + union.containsAll(setA));
        
        // Working with custom objects in HashSet
        System.out.println("\n=== Custom Objects in HashSet ===");
        Set students = new HashSet<>();
        students.add(new Student(101, "Alice"));
        students.add(new Student(102, "Bob"));
        students.add(new Student(101, "Alice")); // Duplicate based on equals/hashCode
        
        System.out.println("Students set size: " + students.size());
        students.forEach(System.out::println);
    }
    
    static class Student {
        private int id;
        private String name;
        
        public Student(int id, String name) {
            this.id = id;
            this.name = name;
        }
        
        // Must override equals and hashCode for HashSet to work correctly
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return id == student.id;
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
        
        @Override
        public String toString() {
            return "Student{id=" + id + ", name='" + name + "'}";
        }
    }
}
Implementation Ordering Null Elements Performance When to Use
HashSet No ordering Allows one null O(1) for add, remove, contains General-purpose set, don't care about order
LinkedHashSet Insertion order Allows one null Slightly slower than HashSet Need insertion-order iteration
TreeSet Sorted (natural or comparator) No nulls if natural ordering O(log n) for operations Need sorted set, range queries
HashSet Methods Reference
add(E e) Adds element if not present
remove(Object o) Removes element
contains(Object o) Checks for element
size() Returns number of elements
isEmpty() Checks if set is empty
clear() Removes all elements
iterator() Returns iterator
TreeSet Methods Reference
first() Returns first (lowest) element
last() Returns last (highest) element
pollFirst() Removes and returns first
pollLast() Removes and returns last
lower(E e) Greatest element < e
higher(E e) Least element > e
floor(E e) Greatest element ≤ e
ceiling(E e) Least element ≥ e
Important Notes for Sets:
  • equals() and hashCode(): Must be properly overridden for custom objects in HashSet/LinkedHashSet
  • Comparable/Comparator: Required for TreeSet elements
  • HashSet performance: Depends on good hashCode() implementation
  • Null values: HashSet/LinkedHashSet allow one null, TreeSet may not allow nulls
  • Thread safety: None are thread-safe (use Collections.synchronizedSet())
  • Backing: HashSet uses HashMap, LinkedHashSet uses LinkedHashMap, TreeSet uses TreeMap

4. Map Interface and Implementations

The Map interface represents a key-value pair collection. Keys are unique, values can be duplicated. Main implementations: HashMap, LinkedHashMap, TreeMap, and Hashtable.

MapExamples.java
import java.util.*;

public class MapExamples {
    public static void main(String[] args) {
        System.out.println("=== HashMap Examples ===");
        
        // HashMap - unordered, allows one null key and multiple null values
        Map hashMap = new HashMap<>();
        
        // Adding key-value pairs
        hashMap.put("Apple", 10);
        hashMap.put("Banana", 20);
        hashMap.put("Cherry", 30);
        hashMap.put("Apple", 15);  // Updates existing key
        hashMap.put(null, 5);      // Allows null key
        hashMap.put("Date", null); // Allows null value
        
        System.out.println("HashMap: " + hashMap);
        System.out.println("Size: " + hashMap.size());
        System.out.println("Is empty: " + hashMap.isEmpty());
        
        // Access operations
        System.out.println("\n=== Access Operations ===");
        System.out.println("Value for 'Apple': " + hashMap.get("Apple"));
        System.out.println("Value for 'Mango' (default): " + hashMap.getOrDefault("Mango", 0));
        System.out.println("Contains key 'Banana': " + hashMap.containsKey("Banana"));
        System.out.println("Contains value 30: " + hashMap.containsValue(30));
        
        // Key set, values, and entry set
        System.out.println("\n=== Views ===");
        System.out.println("Keys: " + hashMap.keySet());
        System.out.println("Values: " + hashMap.values());
        System.out.println("Entries: " + hashMap.entrySet());
        
        // Modification operations
        System.out.println("\n=== Modification Operations ===");
        hashMap.remove("Banana");
        System.out.println("After remove('Banana'): " + hashMap);
        
        hashMap.replace("Cherry", 35);
        System.out.println("After replace('Cherry', 35): " + hashMap);
        
        hashMap.replace("Cherry", 35, 40);  // Replace if old value matches
        System.out.println("After conditional replace: " + hashMap);
        
        hashMap.putIfAbsent("Mango", 25);   // Put if key absent
        System.out.println("After putIfAbsent('Mango', 25): " + hashMap);
        
        // Compute operations (Java 8+)
        System.out.println("\n=== Compute Operations ===");
        hashMap.compute("Apple", (k, v) -> v + 10);  // Compute new value
        System.out.println("After compute on 'Apple': " + hashMap);
        
        hashMap.computeIfAbsent("Orange", k -> 50);  // Compute if absent
        System.out.println("After computeIfAbsent('Orange'): " + hashMap);
        
        hashMap.computeIfPresent("Cherry", (k, v) -> v * 2);  // Compute if present
        System.out.println("After computeIfPresent('Cherry'): " + hashMap);
        
        hashMap.merge("Apple", 5, (oldVal, newVal) -> oldVal + newVal);  // Merge
        System.out.println("After merge on 'Apple': " + hashMap);
        
        // Iteration
        System.out.println("\n=== Iteration Methods ===");
        System.out.println("Using entrySet:");
        for (Map.Entry entry : hashMap.entrySet()) {
            System.out.println("  Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
        
        System.out.println("\nUsing forEach (Java 8+):");
        hashMap.forEach((key, value) -> System.out.println("  " + key + " -> " + value));
        
        System.out.println("\nUsing keySet:");
        for (String key : hashMap.keySet()) {
            System.out.println("  Key: " + key + ", Value: " + hashMap.get(key));
        }
        
        System.out.println("\n=== LinkedHashMap Examples ===");
        
        // LinkedHashMap - maintains insertion order
        Map linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("Zebra", 100);
        linkedHashMap.put("Apple", 200);
        linkedHashMap.put("Monkey", 300);
        
        System.out.println("LinkedHashMap (insertion order): " + linkedHashMap);
        
        // Access-order LinkedHashMap (for LRU cache)
        Map accessOrderMap = new LinkedHashMap<>(16, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > 3;  // Remove eldest when size > 3
            }
        };
        
        accessOrderMap.put("A", 1);
        accessOrderMap.put("B", 2);
        accessOrderMap.put("C", 3);
        accessOrderMap.get("A");  // Access moves to end
        accessOrderMap.put("D", 4); // Should remove eldest (B)
        
        System.out.println("LRU LinkedHashMap: " + accessOrderMap);
        
        System.out.println("\n=== TreeMap Examples ===");
        
        // TreeMap - sorted by keys
        Map treeMap = new TreeMap<>();
        treeMap.put("Orange", 50);
        treeMap.put("Apple", 30);
        treeMap.put("Banana", 40);
        treeMap.put("Cherry", 60);
        
        System.out.println("TreeMap (sorted by keys): " + treeMap);
        
        // TreeMap with custom comparator
        Map reverseTreeMap = new TreeMap<>(Collections.reverseOrder());
        reverseTreeMap.putAll(treeMap);
        System.out.println("TreeMap (reverse order): " + reverseTreeMap);
        
        // TreeMap specific operations
        System.out.println("\n=== TreeMap Specific Operations ===");
        TreeMap numberMap = new TreeMap<>();
        numberMap.put(10, "Ten");
        numberMap.put(5, "Five");
        numberMap.put(15, "Fifteen");
        numberMap.put(3, "Three");
        numberMap.put(7, "Seven");
        
        System.out.println("Number TreeMap: " + numberMap);
        System.out.println("First key: " + numberMap.firstKey());
        System.out.println("Last key: " + numberMap.lastKey());
        System.out.println("First entry: " + numberMap.firstEntry());
        System.out.println("Last entry: " + numberMap.lastEntry());
        
        // Navigable map operations
        System.out.println("\n=== Navigable Map Operations ===");
        System.out.println("Lower key than 8: " + numberMap.lowerKey(8));      // greatest key < 8
        System.out.println("Higher key than 8: " + numberMap.higherKey(8));    // least key > 8
        System.out.println("Floor key of 8: " + numberMap.floorKey(8));        // greatest key ≤ 8
        System.out.println("Ceiling key of 8: " + numberMap.ceilingKey(8));    // least key ≥ 8
        
        // Submap operations
        System.out.println("\n=== Submap Operations ===");
        System.out.println("HeadMap (<10): " + numberMap.headMap(10));          // keys < 10
        System.out.println("TailMap (≥10): " + numberMap.tailMap(10));          // keys ≥ 10
        System.out.println("SubMap (5 to 15): " + numberMap.subMap(5, 15));     // 5 ≤ keys < 15
        
        // Poll operations
        System.out.println("\n=== Poll Operations ===");
        System.out.println("Poll first entry: " + numberMap.pollFirstEntry());
        System.out.println("Poll last entry: " + numberMap.pollLastEntry());
        System.out.println("After polling: " + numberMap);
        
        // Working with custom objects as keys
        System.out.println("\n=== Custom Objects as Map Keys ===");
        Map employeeMap = new HashMap<>();
        employeeMap.put(new Employee(101, "Alice"), "Developer");
        employeeMap.put(new Employee(102, "Bob"), "Manager");
        employeeMap.put(new Employee(101, "Alice"), "Senior Developer"); // Updates
        
        System.out.println("Employee Map size: " + employeeMap.size());
        employeeMap.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
    
    static class Employee {
        private int id;
        private String name;
        
        public Employee(int id, String name) {
            this.id = id;
            this.name = name;
        }
        
        // Must override equals and hashCode for HashMap key
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Employee employee = (Employee) o;
            return id == employee.id;
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
        
        @Override
        public String toString() {
            return "Employee{id=" + id + ", name='" + name + "'}";
        }
    }
}
Implementation Ordering Null Keys/Values Performance Thread Safe
HashMap No ordering 1 null key, multiple null values O(1) average No
LinkedHashMap Insertion/access order 1 null key, multiple null values Slightly slower than HashMap No
TreeMap Sorted by keys No null keys if natural ordering O(log n) No
Hashtable No ordering No null keys or values O(1) average Yes (legacy)
HashMap Methods
put(K, V) Adds/replaces key-value
get(K) Returns value for key
remove(K) Removes key-value pair
containsKey(K) Checks if key exists
containsValue(V) Checks if value exists
TreeMap Methods
firstKey() Returns first key
lastKey() Returns last key
lowerKey(K) Key strictly less than
higherKey(K) Key strictly greater than
pollFirstEntry() Removes first entry
LinkedHashMap
Constructor options:
new LinkedHashMap() Insertion order
new LinkedHashMap(16, 0.75f, true) Access order (LRU)
Use for: LRU cache, predictable iteration
HashMap vs TreeMap vs LinkedHashMap:
  • Use HashMap when: No ordering needed, fastest operations, most common use case
  • Use TreeMap when: Need sorted keys, range queries, floor/ceiling operations
  • Use LinkedHashMap when: Need insertion-order or access-order iteration, LRU cache implementation
  • Performance: HashMap O(1), TreeMap O(log n), LinkedHashMap slightly slower than HashMap
  • Memory: HashMap uses less memory than LinkedHashMap
  • Thread safety: None are thread-safe (use ConcurrentHashMap or Collections.synchronizedMap())

5. Queue Interface and Implementations

The Queue interface represents a collection designed for holding elements prior to processing. Follows FIFO (First-In-First-Out) or priority ordering. Main implementations: PriorityQueue, ArrayDeque.

QueueExamples.java
import java.util.*;

public class QueueExamples {
    public static void main(String[] args) {
        System.out.println("=== Queue Interface Basics ===");
        
        // Queue operations follow FIFO (First-In-First-Out)
        Queue queue = new LinkedList<>();
        
        // Adding elements
        queue.add("First");     // Throws exception if full
        queue.offer("Second");  // Returns false if full
        queue.offer("Third");
        queue.offer("Fourth");
        
        System.out.println("Queue: " + queue);
        System.out.println("Size: " + queue.size());
        System.out.println("Is empty: " + queue.isEmpty());
        
        // Examining elements
        System.out.println("\n=== Examining Elements ===");
        System.out.println("Peek (head): " + queue.peek());    // Returns null if empty
        System.out.println("Element (head): " + queue.element()); // Throws exception if empty
        
        // Removing elements
        System.out.println("\n=== Removing Elements ===");
        System.out.println("Poll (remove head): " + queue.poll());    // Returns null if empty
        System.out.println("Remove (remove head): " + queue.remove()); // Throws exception if empty
        System.out.println("Queue after removals: " + queue);
        
        // Clear queue
        queue.clear();
        System.out.println("After clear, is empty: " + queue.isEmpty());
        
        System.out.println("\n=== PriorityQueue Examples ===");
        
        // PriorityQueue - elements ordered by natural ordering or comparator
        Queue priorityQueue = new PriorityQueue<>();
        
        priorityQueue.offer(30);
        priorityQueue.offer(10);
        priorityQueue.offer(20);
        priorityQueue.offer(5);
        priorityQueue.offer(25);
        
        System.out.println("PriorityQueue (min-heap by default): " + priorityQueue);
        System.out.println("Note: toString() doesn't show priority order!");
        
        System.out.println("\nProcessing in priority order:");
        while (!priorityQueue.isEmpty()) {
            System.out.print(priorityQueue.poll() + " ");  // Removes in sorted order
        }
        
        // PriorityQueue with custom comparator (max-heap)
        System.out.println("\n\n=== Max-Heap PriorityQueue ===");
        Queue maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        maxHeap.addAll(Arrays.asList(30, 10, 20, 5, 25));
        
        System.out.println("Processing max-heap (largest first):");
        while (!maxHeap.isEmpty()) {
            System.out.print(maxHeap.poll() + " ");
        }
        
        // PriorityQueue with custom objects
        System.out.println("\n\n=== PriorityQueue with Custom Objects ===");
        Queue studentQueue = new PriorityQueue<>(Comparator.comparing(Student::getGrade));
        
        studentQueue.offer(new Student("Alice", 85));
        studentQueue.offer(new Student("Bob", 92));
        studentQueue.offer(new Student("Charlie", 78));
        studentQueue.offer(new Student("Diana", 95));
        
        System.out.println("Students by grade (lowest first):");
        while (!studentQueue.isEmpty()) {
            System.out.println(studentQueue.poll());
        }
        
        System.out.println("\n=== ArrayDeque Examples ===");
        
        // ArrayDeque - resizable array implementation of Deque
        Deque deque = new ArrayDeque<>();
        
        // Add operations
        deque.addFirst("Front");    // Add to front
        deque.addLast("Middle");    // Add to end
        deque.offerFirst("Very Front");
        deque.offerLast("End");
        
        System.out.println("Deque: " + deque);
        
        // Examine operations
        System.out.println("First element: " + deque.getFirst());
        System.out.println("Last element: " + deque.getLast());
        System.out.println("Peek first: " + deque.peekFirst());
        System.out.println("Peek last: " + deque.peekLast());
        
        // Remove operations
        System.out.println("\n=== Remove Operations ===");
        System.out.println("Remove first: " + deque.removeFirst());
        System.out.println("Remove last: " + deque.removeLast());
        System.out.println("Poll first: " + deque.pollFirst());
        System.out.println("Poll last: " + deque.pollLast());
        
        System.out.println("Deque after removals: " + deque);
        
        // Stack operations using Deque (recommended over Stack class)
        System.out.println("\n=== Stack Operations (LIFO) ===");
        Deque stack = new ArrayDeque<>();
        
        stack.push(10);  // Push to top
        stack.push(20);
        stack.push(30);
        
        System.out.println("Stack: " + stack);
        System.out.println("Peek (top): " + stack.peek());
        System.out.println("Pop (remove top): " + stack.pop());
        System.out.println("After pop: " + stack);
        
        // Queue operations using Deque
        System.out.println("\n=== Queue Operations (FIFO) using Deque ===");
        Deque fifoQueue = new ArrayDeque<>();
        
        fifoQueue.offerLast("First");
        fifoQueue.offerLast("Second");
        fifoQueue.offerLast("Third");
        
        System.out.println("Queue: " + fifoQueue);
        System.out.println("Process in FIFO order:");
        while (!fifoQueue.isEmpty()) {
            System.out.println(fifoQueue.pollFirst());
        }
        
        // Deque as sliding window
        System.out.println("\n=== Deque for Sliding Window Maximum ===");
        int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
        int k = 3;
        System.out.println("Sliding window maximum for window size " + k + ":");
        System.out.println(Arrays.toString(maxSlidingWindow(nums, k)));
    }
    
    static class Student {
        private String name;
        private int grade;
        
        public Student(String name, int grade) {
            this.name = name;
            this.grade = grade;
        }
        
        public String getName() { return name; }
        public int getGrade() { return grade; }
        
        @Override
        public String toString() {
            return name + " (" + grade + ")";
        }
    }
    
    // Sliding window maximum using Deque
    public static int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || k <= 0) return new int[0];
        
        int n = nums.length;
        int[] result = new int[n - k + 1];
        Deque deque = new ArrayDeque<>();
        
        for (int i = 0; i < n; i++) {
            // Remove indices out of window
            while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
                deque.pollFirst();
            }
            
            // Remove smaller elements from back
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
            
            deque.offerLast(i);
            
            // Add to result when window is formed
            if (i >= k - 1) {
                result[i - k + 1] = nums[deque.peekFirst()];
            }
        }
        
        return result;
    }
}
Implementation Ordering Null Elements Performance Use Cases
PriorityQueue Priority (natural or comparator) Not allowed O(log n) for insert/remove Task scheduling, Dijkstra's algorithm
ArrayDeque FIFO/LIFO (both ends) Not allowed O(1) for add/remove at ends Stack, Queue, Deque implementations
LinkedList FIFO (as Queue) Allowed O(1) for add/remove at ends Simple queue, when nulls needed
PriorityQueue Methods
offer(E e) Inserts element
poll() Retrieves and removes head
peek() Retrieves head without removing
comparator() Returns comparator used
Note: Not thread-safe, use PriorityBlockingQueue for thread safety
Deque Methods
addFirst(E)/addLast(E) Adds to front/back
removeFirst()/removeLast() Removes from front/back
getFirst()/getLast() Returns front/back element
push(E)/pop() Stack operations
Use ArrayDeque for stack/queue, not Stack class

6. Iterators, Comparators and Utilities

Java Collections Framework provides powerful utilities for iterating over collections and defining custom ordering through iterators, comparators, and the Collections utility class.

IteratorExamples.java
import java.util.*;

public class IteratorExamples {
    public static void main(String[] args) {
        System.out.println("=== Iterator Examples ===");
        
        List fruits = new ArrayList<>(Arrays.asList(
            "Apple", "Banana", "Cherry", "Date", "Elderberry"
        ));
        
        // Basic Iterator
        System.out.println("Using Iterator:");
        Iterator iterator = fruits.iterator();
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.print(fruit + " ");
            
            // Remove during iteration
            if (fruit.startsWith("B")) {
                iterator.remove();  // Safe removal
            }
        }
        System.out.println("\nAfter removing 'B' fruits: " + fruits);
        
        // ListIterator (bidirectional)
        System.out.println("\n=== ListIterator (Bidirectional) ===");
        ListIterator listIterator = fruits.listIterator();
        
        // Forward iteration
        System.out.print("Forward: ");
        while (listIterator.hasNext()) {
            System.out.print(listIterator.next() + " ");
        }
        
        // Backward iteration
        System.out.print("\nBackward: ");
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous() + " ");
        }
        
        // ListIterator modification
        listIterator = fruits.listIterator();
        listIterator.next();  // Move to first
        listIterator.add("Apricot");  // Add at current position
        listIterator.next();
        listIterator.set("Blueberry");  // Replace current
        
        System.out.println("\nAfter ListIterator modifications: " + fruits);
        
        // For-each (enhanced for loop) - internally uses iterator
        System.out.print("\nFor-each loop: ");
        for (String fruit : fruits) {
            System.out.print(fruit + " ");
        }
        
        // Java 8+ forEach with lambda
        System.out.print("\nforEach with lambda: ");
        fruits.forEach(fruit -> System.out.print(fruit + " "));
        
        System.out.print("\nforEach with method reference: ");
        fruits.forEach(System.out::print);
        
        // Iterator for Set
        System.out.println("\n\n=== Iterator for Set ===");
        Set numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40, 50));
        Iterator setIterator = numbers.iterator();
        
        System.out.print("Set iteration: ");
        while (setIterator.hasNext()) {
            System.out.print(setIterator.next() + " ");
        }
        
        // Iterator for Map (entrySet, keySet, values)
        System.out.println("\n\n=== Iterator for Map ===");
        Map map = new HashMap<>();
        map.put("Apple", 10);
        map.put("Banana", 20);
        map.put("Cherry", 30);
        
        System.out.println("Using entrySet iterator:");
        Iterator> mapIterator = map.entrySet().iterator();
        while (mapIterator.hasNext()) {
            Map.Entry entry = mapIterator.next();
            System.out.println("  " + entry.getKey() + " -> " + entry.getValue());
            if (entry.getValue() == 20) {
                mapIterator.remove();  // Safe removal
            }
        }
        
        System.out.println("After removing value 20: " + map);
        
        // Fail-fast vs Fail-safe iterators
        System.out.println("\n=== Fail-Fast Iterator Example ===");
        List numbersList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        Iterator failFastIterator = numbersList.iterator();
        
        try {
            while (failFastIterator.hasNext()) {
                Integer num = failFastIterator.next();
                System.out.print(num + " ");
                if (num == 3) {
                    numbersList.add(99);  // Concurrent modification
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("\nConcurrentModificationException caught!");
        }
        
        // Using CopyOnWriteArrayList for fail-safe iteration
        System.out.println("\n=== Fail-Safe Iterator (CopyOnWriteArrayList) ===");
        List copyOnWriteList = new java.util.concurrent.CopyOnWriteArrayList<>(
            Arrays.asList(1, 2, 3, 4, 5)
        );
        
        Iterator failSafeIterator = copyOnWriteList.iterator();
        while (failSafeIterator.hasNext()) {
            Integer num = failSafeIterator.next();
            System.out.print(num + " ");
            if (num == 3) {
                copyOnWriteList.add(99);  // Modification doesn't affect iterator
            }
        }
        
        System.out.println("\nOriginal list after iteration: " + copyOnWriteList);
    }
}
ComparatorExamples.java
import java.util.*;

public class ComparatorExamples {
    public static void main(String[] args) {
        System.out.println("=== Comparator Examples ===");
        
        List names = new ArrayList<>(Arrays.asList(
            "John", "Alice", "Bob", "Charlie", "David"
        ));
        
        // Natural ordering (String implements Comparable)
        System.out.println("Original: " + names);
        Collections.sort(names);
        System.out.println("Natural order: " + names);
        
        // Using Comparator for custom ordering
        Collections.sort(names, new ReverseStringComparator());
        System.out.println("Reverse order: " + names);
        
        // Using lambda expression (Java 8+)
        Collections.sort(names, (a, b) -> a.length() - b.length());
        System.out.println("By length: " + names);
        
        // Using Comparator.comparing (Java 8+)
        names.sort(Comparator.comparing(String::length));
        System.out.println("By length (comparing): " + names);
        
        // Chaining comparators
        names.sort(Comparator.comparing(String::length)
                            .thenComparing(Comparator.naturalOrder()));
        System.out.println("By length then natural: " + names);
        
        // Working with custom objects
        System.out.println("\n=== Custom Objects Sorting ===");
        List employees = Arrays.asList(
            new Employee("Alice", "Developer", 75000, 28),
            new Employee("Bob", "Manager", 90000, 35),
            new Employee("Charlie", "Developer", 80000, 30),
            new Employee("David", "Manager", 95000, 40),
            new Employee("Eve", "Developer", 70000, 25)
        );
        
        System.out.println("Original employees:");
        employees.forEach(System.out::println);
        
        // Sort by salary
        employees.sort(Comparator.comparingInt(Employee::getSalary));
        System.out.println("\nSorted by salary:");
        employees.forEach(System.out::println);
        
        // Sort by salary descending
        employees.sort(Comparator.comparingInt(Employee::getSalary).reversed());
        System.out.println("\nSorted by salary descending:");
        employees.forEach(System.out::println);
        
        // Sort by department then salary
        employees.sort(Comparator.comparing(Employee::getDepartment)
                                .thenComparingInt(Employee::getSalary));
        System.out.println("\nSorted by department then salary:");
        employees.forEach(System.out::println);
        
        // Sort by age then name
        employees.sort(Comparator.comparingInt(Employee::getAge)
                                .thenComparing(Employee::getName));
        System.out.println("\nSorted by age then name:");
        employees.forEach(System.out::println);
        
        // Handling null values
        System.out.println("\n=== Handling Null Values ===");
        List namesWithNulls = new ArrayList<>(Arrays.asList(
            "John", null, "Alice", null, "Bob", "Charlie"
        ));
        
        System.out.println("With nulls: " + namesWithNulls);
        
        // Nulls first
        namesWithNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println("Nulls first: " + namesWithNulls);
        
        // Nulls last
        namesWithNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
        System.out.println("Nulls last: " + namesWithNulls);
        
        // Using TreeSet with Comparator
        System.out.println("\n=== TreeSet with Comparator ===");
        Set employeeSet = new TreeSet<>(
            Comparator.comparing(Employee::getDepartment)
                     .thenComparing(Employee::getName)
        );
        
        employeeSet.addAll(employees);
        System.out.println("TreeSet sorted by department then name:");
        employeeSet.forEach(System.out::println);
        
        // Collections utility methods
        System.out.println("\n=== Collections Utility Methods ===");
        List numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3, 7));
        
        System.out.println("Original: " + numbers);
        Collections.reverse(numbers);
        System.out.println("Reversed: " + numbers);
        
        Collections.shuffle(numbers);
        System.out.println("Shuffled: " + numbers);
        
        Collections.sort(numbers);
        System.out.println("Sorted: " + numbers);
        
        System.out.println("Max: " + Collections.max(numbers));
        System.out.println("Min: " + Collections.min(numbers));
        System.out.println("Frequency of 3: " + Collections.frequency(numbers, 3));
        
        Collections.fill(numbers.subList(0, 3), 0);
        System.out.println("After fill: " + numbers);
    }
    
    static class ReverseStringComparator implements Comparator {
        @Override
        public int compare(String s1, String s2) {
            return s2.compareTo(s1);  // Reverse of natural order
        }
    }
    
    static class Employee {
        private String name;
        private String department;
        private int salary;
        private int age;
        
        public Employee(String name, String department, int salary, int age) {
            this.name = name;
            this.department = department;
            this.salary = salary;
            this.age = age;
        }
        
        public String getName() { return name; }
        public String getDepartment() { return department; }
        public int getSalary() { return salary; }
        public int getAge() { return age; }
        
        @Override
        public String toString() {
            return String.format("%-10s %-12s $%-8d %2d", 
                name, department, salary, age);
        }
    }
}
Interface Methods Description Use Cases
Iterator hasNext()
next()
remove()
Forward-only iteration, safe removal Generic collection iteration
ListIterator hasPrevious()
previous()
add()
set()
Bidirectional iteration, modification List traversal in both directions
Comparator compare(T o1, T o2)
equals(Object obj)
Defines custom ordering Sorting collections, TreeSet/TreeMap
Collections Utility Methods
  • sort(List) - Sorts list
  • reverse(List) - Reverses order
  • shuffle(List) - Randomly permutes
  • swap(List, i, j) - Swaps elements
  • binarySearch(List, key) - Searches sorted list
  • max(Collection)/min(Collection)
  • frequency(Collection, o) - Count occurrences
  • synchronizedXxx() - Thread-safe wrappers
  • unmodifiableXxx() - Immutable wrappers
Java 8+ Streams with Collections
  • collection.stream() - Creates stream
  • collection.parallelStream() - Parallel stream
  • stream.filter() - Filter elements
  • stream.map() - Transform elements
  • stream.sorted() - Sort stream
  • stream.collect() - Convert to collection
  • stream.forEach() - Iterate with lambda
  • stream.reduce() - Aggregate operations

7. Performance Comparison and Best Practices

Understanding the performance characteristics of different collections is crucial for choosing the right data structure for your needs.

Time Complexity Comparison

Operation ArrayList LinkedList HashSet TreeSet HashMap TreeMap
Add O(1)* O(1) O(1) O(log n) O(1) O(log n)
Remove O(n) O(n) O(1) O(log n) O(1) O(log n)
Get/Contains O(1) O(n) O(1) O(log n) O(1) O(log n)
Insert at Index O(n) O(1) N/A N/A N/A N/A
Iteration O(n) O(n) O(n) O(n) O(n) O(n)
Memory Overhead Low High Medium Medium Medium Medium

* Amortized constant time for ArrayList add (resizing may cause O(n))

Collection Best Practices
  • Initialize with estimated capacity for ArrayList/HashMap
  • Use generics for type safety
  • Override equals() and hashCode() for custom objects in HashSet/HashMap
  • Implement Comparable for TreeSet/TreeMap elements
  • Use Collections utility methods for common operations
  • Prefer interface types for variable declarations
  • Consider thread safety requirements
Common Mistakes
  • Modifying collection during iteration (except with iterator.remove())
  • Using raw types (without generics)
  • Not overriding equals/hashCode for custom keys in HashMap
  • Using Vector/Hashtable when not needed (legacy, synchronized)
  • Choosing wrong collection for the task
  • Not considering thread safety in multi-threaded environments
  • Using null keys/values where not allowed

Choosing the Right Collection

Need duplicates and ordering? → ArrayList/LinkedList
Need unique elements? → HashSet/LinkedHashSet/TreeSet
Need key-value pairs? → HashMap/LinkedHashMap/TreeMap
Need FIFO/LIFO? → ArrayDeque/LinkedList
Need priority ordering? → PriorityQueue
Need thread safety? → ConcurrentHashMap, CopyOnWriteArrayList
Need immutable collections? → Collections.unmodifiableXxx()

Java 8+ Enhancements:
  • Streams API: Functional-style operations on collections
  • Lambda expressions: Cleaner comparator and iteration code
  • Method references: Further simplification
  • Default methods: New methods in interfaces (forEach, spliterator)
  • Concurrent enhancements: Improved concurrent collections
  • Collection factory methods: List.of(), Set.of(), Map.of() (Java 9+)

8. Practical Collections Examples

Example 1: E-commerce Shopping Cart

import java.util.*;

public class ShoppingCart {
    // Using LinkedHashMap to maintain insertion order
    private Map items = new LinkedHashMap<>();
    
    public void addProduct(String productId, String name, double price, int quantity) {
        items.merge(productId, 
                   new CartItem(productId, name, price, quantity),
                   (oldItem, newItem) -> {
                       oldItem.increaseQuantity(newItem.getQuantity());
                       return oldItem;
                   });
    }
    
    public void removeProduct(String productId) {
        items.remove(productId);
    }
    
    public void updateQuantity(String productId, int newQuantity) {
        CartItem item = items.get(productId);
        if (item != null) {
            if (newQuantity <= 0) {
                removeProduct(productId);
            } else {
                item.setQuantity(newQuantity);
            }
        }
    }
    
    public double getTotalPrice() {
        return items.values().stream()
                   .mapToDouble(CartItem::getTotalPrice)
                   .sum();
    }
    
    public List getItemsSortedByPrice() {
        List sortedItems = new ArrayList<>(items.values());
        sortedItems.sort(Comparator.comparingDouble(CartItem::getPrice).reversed());
        return sortedItems;
    }
    
    public List getItemsSortedByName() {
        List sortedItems = new ArrayList<>(items.values());
        sortedItems.sort(Comparator.comparing(CartItem::getName));
        return sortedItems;
    }
    
    public Map getProductCountByFirstLetter() {
        Map countMap = new TreeMap<>();
        for (CartItem item : items.values()) {
            String firstLetter = item.getName().substring(0, 1).toUpperCase();
            countMap.merge(firstLetter, 1, Integer::sum);
        }
        return countMap;
    }
    
    public void displayCart() {
        System.out.println("\n=== Shopping Cart ===");
        System.out.println("Items in cart:");
        
        items.forEach((id, item) -> 
            System.out.printf("  %-20s $%-8.2f x %d = $%.2f\n",
                item.getName(), item.getPrice(), 
                item.getQuantity(), item.getTotalPrice()));
        
        System.out.println("Total: $" + getTotalPrice());
        
        // Show statistics
        System.out.println("\nCart Statistics:");
        System.out.println("Total items: " + items.size());
        System.out.println("Total quantity: " + 
            items.values().stream().mapToInt(CartItem::getQuantity).sum());
        
        // Most expensive item
        items.values().stream()
             .max(Comparator.comparingDouble(CartItem::getPrice))
             .ifPresent(item -> 
                 System.out.println("Most expensive: " + item.getName() + 
                                  " ($" + item.getPrice() + ")"));
    }
    
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // Add products
        cart.addProduct("P001", "Laptop", 999.99, 1);
        cart.addProduct("P002", "Mouse", 25.50, 2);
        cart.addProduct("P003", "Keyboard", 75.00, 1);
        cart.addProduct("P001", "Laptop", 999.99, 1); // Add more of same
        
        // Display cart
        cart.displayCart();
        
        // Update quantity
        cart.updateQuantity("P002", 3);
        
        // Remove product
        cart.removeProduct("P003");
        
        System.out.println("\n=== After Updates ===");
        cart.displayCart();
        
        // Get sorted items
        System.out.println("\n=== Items Sorted by Price (High to Low) ===");
        cart.getItemsSortedByPrice().forEach(System.out::println);
        
        System.out.println("\n=== Product Count by First Letter ===");
        cart.getProductCountByFirstLetter().forEach((letter, count) -> 
            System.out.println(letter + ": " + count + " products"));
    }
}

class CartItem {
    private String productId;
    private String name;
    private double price;
    private int quantity;
    
    public CartItem(String productId, String name, double price, int quantity) {
        this.productId = productId;
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }
    
    public double getTotalPrice() {
        return price * quantity;
    }
    
    public void increaseQuantity(int amount) {
        this.quantity += amount;
    }
    
    // Getters and setters
    public String getProductId() { return productId; }
    public String getName() { return name; }
    public double getPrice() { return price; }
    public int getQuantity() { return quantity; }
    public void setQuantity(int quantity) { this.quantity = quantity; }
    
    @Override
    public String toString() {
        return String.format("%-15s $%-8.2f x %d", name, price, quantity);
    }
}

Example 2: Employee Management System

import java.util.*;
import java.util.stream.Collectors;

public class EmployeeManagementSystem {
    private Map employees = new HashMap<>();
    private Map departments = new HashMap<>();
    
    public void addEmployee(Employee emp) {
        employees.put(emp.getId(), emp);
        
        // Update department employee count
        departments.computeIfAbsent(emp.getDepartment(), 
                                   k -> new Department(k)).addEmployee(emp);
    }
    
    public void removeEmployee(int id) {
        Employee emp = employees.remove(id);
        if (emp != null) {
            departments.get(emp.getDepartment()).removeEmployee(emp);
        }
    }
    
    public Employee findEmployeeById(int id) {
        return employees.get(id);
    }
    
    public List findEmployeesByName(String name) {
        return employees.values().stream()
                       .filter(e -> e.getName().toLowerCase()
                                   .contains(name.toLowerCase()))
                       .collect(Collectors.toList());
    }
    
    public Map> getEmployeesByDepartment() {
        return employees.values().stream()
                       .collect(Collectors.groupingBy(Employee::getDepartment));
    }
    
    public Map getAverageSalaryByDepartment() {
        return employees.values().stream()
                       .collect(Collectors.groupingBy(
                           Employee::getDepartment,
                           Collectors.averagingDouble(Employee::getSalary)
                       ));
    }
    
    public Map getEmployeeCountByDepartment() {
        return employees.values().stream()
                       .collect(Collectors.groupingBy(
                           Employee::getDepartment,
                           Collectors.counting()
                       ));
    }
    
    public List getTopEarners(int n) {
        return employees.values().stream()
                       .sorted(Comparator.comparingDouble(Employee::getSalary)
                                       .reversed())
                       .limit(n)
                       .collect(Collectors.toList());
    }
    
    public Map getHighestPaidByDepartment() {
        return employees.values().stream()
                       .collect(Collectors.groupingBy(
                           Employee::getDepartment,
                           Collectors.collectingAndThen(
                               Collectors.maxBy(Comparator
                                   .comparingDouble(Employee::getSalary)),
                               Optional::get
                           )
                       ));
    }
    
    public Set getUniqueJobTitles() {
        return employees.values().stream()
                       .map(Employee::getJobTitle)
                       .collect(Collectors.toCollection(TreeSet::new));
    }
    
    public void generateReport() {
        System.out.println("\n=== Employee Management System Report ===");
        System.out.println("Total Employees: " + employees.size());
        
        System.out.println("\n=== Employees by Department ===");
        getEmployeesByDepartment().forEach((dept, empList) -> {
            System.out.println("\n" + dept + " (" + empList.size() + " employees):");
            empList.forEach(e -> System.out.println("  " + e));
        });
        
        System.out.println("\n=== Department Statistics ===");
        getAverageSalaryByDepartment().forEach((dept, avgSalary) -> 
            System.out.printf("%-15s: Average Salary: $%,.2f\n", dept, avgSalary));
        
        System.out.println("\n=== Top 5 Earners ===");
        getTopEarners(5).forEach(e -> 
            System.out.printf("  %-20s $%,.2f\n", e.getName(), e.getSalary()));
        
        System.out.println("\n=== Highest Paid by Department ===");
        getHighestPaidByDepartment().forEach((dept, emp) -> 
            System.out.printf("  %-15s: %-20s $%,.2f\n", 
                dept, emp.getName(), emp.getSalary()));
        
        System.out.println("\n=== Unique Job Titles ===");
        getUniqueJobTitles().forEach(title -> System.out.println("  " + title));
    }
    
    public static void main(String[] args) {
        EmployeeManagementSystem ems = new EmployeeManagementSystem();
        
        // Add sample employees
        ems.addEmployee(new Employee(101, "Alice Johnson", "Engineering", 
                                    "Software Engineer", 85000, 28));
        ems.addEmployee(new Employee(102, "Bob Smith", "Engineering", 
                                    "Senior Engineer", 110000, 35));
        ems.addEmployee(new Employee(103, "Charlie Brown", "Sales", 
                                    "Sales Manager", 95000, 40));
        ems.addEmployee(new Employee(104, "Diana Prince", "Sales", 
                                    "Sales Representative", 65000, 28));
        ems.addEmployee(new Employee(105, "Eve Adams", "HR", 
                                    "HR Manager", 90000, 38));
        ems.addEmployee(new Employee(106, "Frank Miller", "Engineering", 
                                    "Software Engineer", 88000, 30));
        ems.addEmployee(new Employee(107, "Grace Lee", "Marketing", 
                                    "Marketing Specialist", 75000, 32));
        ems.addEmployee(new Employee(108, "Henry Ford", "Engineering", 
                                    "Lead Engineer", 125000, 42));
        
        // Generate report
        ems.generateReport();
        
        // Search examples
        System.out.println("\n=== Search Results ===");
        System.out.println("Employees with 'Smith':");
        ems.findEmployeesByName("Smith").forEach(System.out::println);
        
        System.out.println("\nEmployee with ID 103:");
        System.out.println(ems.findEmployeeById(103));
        
        // Remove an employee
        ems.removeEmployee(104);
        System.out.println("\n=== After removing employee 104 ===");
        System.out.println("Total employees: " + ems.employees.size());
    }
}

class Employee {
    private int id;
    private String name;
    private String department;
    private String jobTitle;
    private double salary;
    private int age;
    
    public Employee(int id, String name, String department, 
                   String jobTitle, double salary, int age) {
        this.id = id;
        this.name = name;
        this.department = department;
        this.jobTitle = jobTitle;
        this.salary = salary;
        this.age = age;
    }
    
    // Getters
    public int getId() { return id; }
    public String getName() { return name; }
    public String getDepartment() { return department; }
    public String getJobTitle() { return jobTitle; }
    public double getSalary() { return salary; }
    public int getAge() { return age; }
    
    @Override
    public String toString() {
        return String.format("%-20s %-15s %-20s $%,.2f %2dy",
            name, department, jobTitle, salary, age);
    }
}

class Department {
    private String name;
    private Set employees = new HashSet<>();
    
    public Department(String name) {
        this.name = name;
    }
    
    public void addEmployee(Employee emp) {
        employees.add(emp);
    }
    
    public void removeEmployee(Employee emp) {
        employees.remove(emp);
    }
    
    public String getName() { return name; }
    public Set getEmployees() { return employees; }
}

9. Collections Framework Best Practices

Common Collections Mistakes:
  1. Choosing wrong collection type for the task
  2. Not overriding equals() and hashCode() for custom objects in HashSet/HashMap
  3. Modifying collections during iteration (except with iterator.remove())
  4. Using raw types instead of generics
  5. Not considering thread safety in multi-threaded applications
  6. Inefficient operations (e.g., contains() on ArrayList instead of HashSet)
  7. Not setting initial capacity for large collections
Collections Best Practices
  • Use generics for type safety
  • Initialize collections with estimated capacity
  • Prefer interface types (List, Set, Map) for variables
  • Use Collections utility methods for common operations
  • Consider performance characteristics when choosing collections
  • Use immutable collections when data shouldn't change
  • Implement Comparable for objects in TreeSet/TreeMap
Performance Optimization Tips
  • Use ArrayList for random access, LinkedList for frequent insertions/deletions
  • Use HashSet/HashMap for O(1) lookups
  • Avoid frequent resizing by setting initial capacity
  • Use parallel streams for large collections on multi-core systems
  • Consider ConcurrentHashMap for concurrent access
  • Use primitive collections (Eclipse Collections, fastutil) for primitive types

Choosing Collections - Quick Reference

For Lists:

  • ArrayList: Default choice, random access
  • LinkedList: Frequent insertions/deletions
  • CopyOnWriteArrayList: Thread-safe, read-heavy
  • Vector: Legacy, synchronized

For Sets/Maps:

  • HashSet/HashMap: Default, O(1) operations
  • LinkedHashSet/LinkedHashMap: Insertion/access order
  • TreeSet/TreeMap: Sorted, O(log n) operations
  • ConcurrentHashMap: Thread-safe map
Java 9+ Factory Methods:

Java 9 introduced convenient factory methods for creating immutable collections:

  • List.of("a", "b", "c") - Immutable list
  • Set.of("a", "b", "c") - Immutable set
  • Map.of("key1", 1, "key2", 2) - Immutable map
  • Map.ofEntries(entry("k1", 1), entry("k2", 2)) - More entries
  • Note: These collections are immutable (cannot add/remove)