Java Collections Framework

Java Collections Framework

The Java Collections Framework provides a comprehensive set of data structures and algorithms for storing, manipulating, and accessing groups of objects. Understanding collections is essential for writing efficient Java applications and handling data effectively.

Prerequisites: This guide assumes familiarity with Java generics, interfaces, and basic OOP concepts. Review OOP Concepts if needed.

Collections Hierarchy Overview

The Collections Framework is built around several key interfaces and their implementations:

 1// Core collection interfaces demonstration
 2import java.util.*;
 3
 4public class CollectionsOverview {
 5    public static void demonstrateHierarchy() {
 6        System.out.println("=== Collections Framework Hierarchy ===");
 7        
 8        // Collection interface - root of most collections
 9        // ├── List interface - ordered collections that allow duplicates
10        // │   ├── ArrayList - resizable array implementation
11        // │   ├── LinkedList - doubly-linked list implementation
12        // │   └── Vector - synchronized resizable array (legacy)
13        //
14        // ├── Set interface - collections that do not allow duplicates
15        // │   ├── HashSet - hash table implementation
16        // │   ├── LinkedHashSet - hash table with insertion order
17        // │   └── TreeSet - sorted set implementation
18        //
19        // └── Queue interface - collections for holding elements prior to processing
20        //     ├── PriorityQueue - priority heap implementation
21        //     ├── ArrayDeque - resizable array deque
22        //     └── LinkedList - also implements Queue
23        //
24        // Map interface - separate hierarchy for key-value pairs
25        // ├── HashMap - hash table implementation
26        // ├── LinkedHashMap - hash table with insertion/access order
27        // ├── TreeMap - sorted map implementation
28        // └── Hashtable - synchronized hash table (legacy)
29        
30        System.out.println("Framework provides consistent APIs across different data structures");
31    }
32}

List Interface and Implementations

Lists maintain insertion order and allow duplicate elements with indexed access.

ArrayList - Dynamic Array

  1import java.util.*;
  2
  3public class ArrayListExamples {
  4    
  5    public static void demonstrateArrayList() {
  6        System.out.println("=== ArrayList Examples ===");
  7        
  8        // Creating and initializing ArrayList
  9        List<String> fruits = new ArrayList<>();
 10        
 11        // Adding elements
 12        fruits.add("Apple");
 13        fruits.add("Banana");
 14        fruits.add("Orange");
 15        fruits.add("Apple");  // Duplicates allowed
 16        
 17        System.out.println("Initial list: " + fruits);
 18        System.out.println("Size: " + fruits.size());
 19        
 20        // Indexed access
 21        String firstFruit = fruits.get(0);
 22        System.out.println("First fruit: " + firstFruit);
 23        
 24        // Modifying elements
 25        fruits.set(1, "Blueberry");  // Replace at index 1
 26        System.out.println("After replacement: " + fruits);
 27        
 28        // Inserting at specific position
 29        fruits.add(2, "Cherry");
 30        System.out.println("After insertion: " + fruits);
 31        
 32        // Searching
 33        int appleIndex = fruits.indexOf("Apple");
 34        int lastAppleIndex = fruits.lastIndexOf("Apple");
 35        boolean hasOrange = fruits.contains("Orange");
 36        
 37        System.out.println("First Apple at index: " + appleIndex);
 38        System.out.println("Last Apple at index: " + lastAppleIndex);
 39        System.out.println("Contains Orange: " + hasOrange);
 40        
 41        // Removing elements
 42        fruits.remove("Orange");           // Remove by value
 43        fruits.remove(0);                  // Remove by index
 44        System.out.println("After removals: " + fruits);
 45        
 46        // Bulk operations
 47        List<String> moreFruits = Arrays.asList("Mango", "Pineapple");
 48        fruits.addAll(moreFruits);
 49        System.out.println("After adding more fruits: " + fruits);
 50        
 51        // Iterating
 52        System.out.println("\nIterating through list:");
 53        for (int i = 0; i < fruits.size(); i++) {
 54            System.out.println(i + ": " + fruits.get(i));
 55        }
 56        
 57        // Enhanced for loop
 58        System.out.println("\nUsing enhanced for loop:");
 59        for (String fruit : fruits) {
 60            System.out.println("- " + fruit);
 61        }
 62        
 63        // Using streams (Java 8+)
 64        System.out.println("\nFruits starting with 'A':");
 65        fruits.stream()
 66              .filter(fruit -> fruit.startsWith("A"))
 67              .forEach(System.out::println);
 68    }
 69    
 70    public static void performanceCharacteristics() {
 71        System.out.println("\n=== ArrayList Performance ===");
 72        
 73        List<Integer> numbers = new ArrayList<>();
 74        
 75        // Adding elements - O(1) amortized
 76        long startTime = System.nanoTime();
 77        for (int i = 0; i < 100000; i++) {
 78            numbers.add(i);
 79        }
 80        long endTime = System.nanoTime();
 81        System.out.println("Adding 100,000 elements: " + (endTime - startTime) / 1_000_000 + " ms");
 82        
 83        // Random access - O(1)
 84        startTime = System.nanoTime();
 85        for (int i = 0; i < 10000; i++) {
 86            int randomIndex = (int) (Math.random() * numbers.size());
 87            numbers.get(randomIndex);
 88        }
 89        endTime = System.nanoTime();
 90        System.out.println("10,000 random accesses: " + (endTime - startTime) / 1_000_000 + " ms");
 91        
 92        // Inserting at beginning - O(n)
 93        startTime = System.nanoTime();
 94        for (int i = 0; i < 1000; i++) {
 95            numbers.add(0, -i);  // Insert at beginning
 96        }
 97        endTime = System.nanoTime();
 98        System.out.println("1,000 insertions at beginning: " + (endTime - startTime) / 1_000_000 + " ms");
 99    }
100}

LinkedList - Doubly Linked List

 1import java.util.*;
 2
 3public class LinkedListExamples {
 4    
 5    public static void demonstrateLinkedList() {
 6        System.out.println("=== LinkedList Examples ===");
 7        
 8        LinkedList<String> tasks = new LinkedList<>();
 9        
10        // Adding elements
11        tasks.add("Complete project");
12        tasks.add("Review code");
13        tasks.add("Write documentation");
14        
15        // LinkedList specific methods
16        tasks.addFirst("Plan meeting");     // Add to beginning
17        tasks.addLast("Deploy application"); // Add to end
18        
19        System.out.println("Task list: " + tasks);
20        
21        // Accessing first and last elements
22        String firstTask = tasks.getFirst();
23        String lastTask = tasks.getLast();
24        System.out.println("First task: " + firstTask);
25        System.out.println("Last task: " + lastTask);
26        
27        // Removing from ends
28        String removedFirst = tasks.removeFirst();
29        String removedLast = tasks.removeLast();
30        System.out.println("Removed first: " + removedFirst);
31        System.out.println("Removed last: " + removedLast);
32        System.out.println("Remaining tasks: " + tasks);
33        
34        // Using as Queue (FIFO)
35        Queue<String> taskQueue = new LinkedList<>();
36        taskQueue.offer("Task 1");  // Add to rear
37        taskQueue.offer("Task 2");
38        taskQueue.offer("Task 3");
39        
40        System.out.println("\nProcessing tasks from queue:");
41        while (!taskQueue.isEmpty()) {
42            String currentTask = taskQueue.poll();  // Remove from front
43            System.out.println("Processing: " + currentTask);
44        }
45        
46        // Using as Stack (LIFO)
47        Deque<String> taskStack = new LinkedList<>();
48        taskStack.push("Task A");  // Add to top
49        taskStack.push("Task B");
50        taskStack.push("Task C");
51        
52        System.out.println("\nProcessing tasks from stack:");
53        while (!taskStack.isEmpty()) {
54            String currentTask = taskStack.pop();  // Remove from top
55            System.out.println("Processing: " + currentTask);
56        }
57    }
58    
59    public static void compareListPerformance() {
60        System.out.println("\n=== ArrayList vs LinkedList Performance ===");
61        
62        int size = 100000;
63        
64        // ArrayList performance
65        List<Integer> arrayList = new ArrayList<>();
66        long startTime = System.nanoTime();
67        for (int i = 0; i < size; i++) {
68            arrayList.add(0, i);  // Insert at beginning
69        }
70        long arrayListTime = System.nanoTime() - startTime;
71        
72        // LinkedList performance
73        List<Integer> linkedList = new LinkedList<>();
74        startTime = System.nanoTime();
75        for (int i = 0; i < size; i++) {
76            linkedList.add(0, i);  // Insert at beginning
77        }
78        long linkedListTime = System.nanoTime() - startTime;
79        
80        System.out.println("ArrayList insertions at beginning: " + arrayListTime / 1_000_000 + " ms");
81        System.out.println("LinkedList insertions at beginning: " + linkedListTime / 1_000_000 + " ms");
82        
83        // Random access comparison
84        startTime = System.nanoTime();
85        for (int i = 0; i < 10000; i++) {
86            arrayList.get(i);
87        }
88        long arrayListAccessTime = System.nanoTime() - startTime;
89        
90        startTime = System.nanoTime();
91        for (int i = 0; i < 10000; i++) {
92            linkedList.get(i);
93        }
94        long linkedListAccessTime = System.nanoTime() - startTime;
95        
96        System.out.println("ArrayList random access: " + arrayListAccessTime / 1_000_000 + " ms");
97        System.out.println("LinkedList random access: " + linkedListAccessTime / 1_000_000 + " ms");
98    }
99}

Set Interface and Implementations

Sets ensure uniqueness of elements and provide efficient membership testing.

HashSet, LinkedHashSet, and TreeSet

  1import java.util.*;
  2
  3public class SetExamples {
  4    
  5    public static void demonstrateSets() {
  6        System.out.println("=== Set Implementations Comparison ===");
  7        
  8        // HashSet - no ordering guarantee, fastest for basic operations
  9        Set<String> hashSet = new HashSet<>();
 10        hashSet.addAll(Arrays.asList("Banana", "Apple", "Cherry", "Apple", "Date"));
 11        System.out.println("HashSet: " + hashSet);  // Order may vary
 12        
 13        // LinkedHashSet - maintains insertion order
 14        Set<String> linkedHashSet = new LinkedHashSet<>();
 15        linkedHashSet.addAll(Arrays.asList("Banana", "Apple", "Cherry", "Apple", "Date"));
 16        System.out.println("LinkedHashSet: " + linkedHashSet);  // Insertion order preserved
 17        
 18        // TreeSet - maintains sorted order
 19        Set<String> treeSet = new TreeSet<>();
 20        treeSet.addAll(Arrays.asList("Banana", "Apple", "Cherry", "Apple", "Date"));
 21        System.out.println("TreeSet: " + treeSet);  // Natural order (alphabetical)
 22        
 23        System.out.println("\n=== Set Operations ===");
 24        
 25        Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
 26        Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
 27        
 28        // Union
 29        Set<Integer> union = new HashSet<>(set1);
 30        union.addAll(set2);
 31        System.out.println("Set1: " + set1);
 32        System.out.println("Set2: " + set2);
 33        System.out.println("Union: " + union);
 34        
 35        // Intersection
 36        Set<Integer> intersection = new HashSet<>(set1);
 37        intersection.retainAll(set2);
 38        System.out.println("Intersection: " + intersection);
 39        
 40        // Difference
 41        Set<Integer> difference = new HashSet<>(set1);
 42        difference.removeAll(set2);
 43        System.out.println("Difference (set1 - set2): " + difference);
 44        
 45        // Subset testing
 46        Set<Integer> subset = new HashSet<>(Arrays.asList(1, 2, 3));
 47        boolean isSubset = set1.containsAll(subset);
 48        System.out.println("Is {1,2,3} subset of set1? " + isSubset);
 49    }
 50    
 51    public static void demonstrateCustomObjects() {
 52        System.out.println("\n=== Sets with Custom Objects ===");
 53        
 54        // Student class for demonstration
 55        class Student {
 56            private String name;
 57            private int id;
 58            
 59            public Student(String name, int id) {
 60                this.name = name;
 61                this.id = id;
 62            }
 63            
 64            @Override
 65            public boolean equals(Object obj) {
 66                if (this == obj) return true;
 67                if (obj == null || getClass() != obj.getClass()) return false;
 68                Student student = (Student) obj;
 69                return id == student.id && Objects.equals(name, student.name);
 70            }
 71            
 72            @Override
 73            public int hashCode() {
 74                return Objects.hash(name, id);
 75            }
 76            
 77            @Override
 78            public String toString() {
 79                return String.format("Student{name='%s', id=%d}", name, id);
 80            }
 81        }
 82        
 83        Set<Student> students = new HashSet<>();
 84        students.add(new Student("Alice", 123));
 85        students.add(new Student("Bob", 456));
 86        students.add(new Student("Alice", 123));  // Duplicate - won't be added
 87        
 88        System.out.println("Students set: " + students);
 89        System.out.println("Set size: " + students.size());
 90        
 91        // TreeSet with custom comparator
 92        Set<Student> sortedStudents = new TreeSet<>((s1, s2) -> s1.name.compareTo(s2.name));
 93        sortedStudents.addAll(Arrays.asList(
 94            new Student("Charlie", 789),
 95            new Student("Alice", 123),
 96            new Student("Bob", 456)
 97        ));
 98        System.out.println("Sorted students: " + sortedStudents);
 99    }
100}

Map Interface and Implementations

Maps store key-value pairs and provide efficient lookup, insertion, and deletion operations.

HashMap, LinkedHashMap, and TreeMap

  1import java.util.*;
  2
  3public class MapExamples {
  4    
  5    public static void demonstrateMaps() {
  6        System.out.println("=== Map Implementations ===");
  7        
  8        // HashMap - fastest for basic operations, no ordering
  9        Map<String, Integer> hashMap = new HashMap<>();
 10        hashMap.put("Alice", 85);
 11        hashMap.put("Bob", 92);
 12        hashMap.put("Charlie", 78);
 13        hashMap.put("Diana", 95);
 14        
 15        System.out.println("HashMap: " + hashMap);
 16        
 17        // LinkedHashMap - maintains insertion order
 18        Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
 19        linkedHashMap.put("Alice", 85);
 20        linkedHashMap.put("Bob", 92);
 21        linkedHashMap.put("Charlie", 78);
 22        linkedHashMap.put("Diana", 95);
 23        
 24        System.out.println("LinkedHashMap: " + linkedHashMap);
 25        
 26        // TreeMap - maintains sorted order by keys
 27        Map<String, Integer> treeMap = new TreeMap<>();
 28        treeMap.put("Alice", 85);
 29        treeMap.put("Bob", 92);
 30        treeMap.put("Charlie", 78);
 31        treeMap.put("Diana", 95);
 32        
 33        System.out.println("TreeMap: " + treeMap);
 34        
 35        System.out.println("\n=== Map Operations ===");
 36        
 37        Map<String, Integer> scores = new HashMap<>();
 38        scores.put("Math", 95);
 39        scores.put("Science", 88);
 40        scores.put("English", 92);
 41        scores.put("History", 85);
 42        
 43        // Basic operations
 44        Integer mathScore = scores.get("Math");
 45        Integer artScore = scores.get("Art");  // Returns null if key doesn't exist
 46        Integer defaultScore = scores.getOrDefault("Art", 0);  // Returns default if key doesn't exist
 47        
 48        System.out.println("Math score: " + mathScore);
 49        System.out.println("Art score: " + artScore);
 50        System.out.println("Art score (with default): " + defaultScore);
 51        
 52        // Checking for keys and values
 53        boolean hasMath = scores.containsKey("Math");
 54        boolean hasScore95 = scores.containsValue(95);
 55        System.out.println("Has Math: " + hasMath);
 56        System.out.println("Has score 95: " + hasScore95);
 57        
 58        // Iterating over maps
 59        System.out.println("\nIterating over key-value pairs:");
 60        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
 61            System.out.println(entry.getKey() + ": " + entry.getValue());
 62        }
 63        
 64        System.out.println("\nIterating over keys:");
 65        for (String subject : scores.keySet()) {
 66            System.out.println(subject + " -> " + scores.get(subject));
 67        }
 68        
 69        System.out.println("\nIterating over values:");
 70        for (Integer score : scores.values()) {
 71            System.out.println("Score: " + score);
 72        }
 73        
 74        // Java 8 forEach
 75        System.out.println("\nUsing Java 8 forEach:");
 76        scores.forEach((subject, score) -> 
 77            System.out.println(subject + " grade: " + getLetterGrade(score)));
 78    }
 79    
 80    private static String getLetterGrade(int score) {
 81        if (score >= 90) return "A";
 82        if (score >= 80) return "B";
 83        if (score >= 70) return "C";
 84        if (score >= 60) return "D";
 85        return "F";
 86    }
 87    
 88    public static void advancedMapOperations() {
 89        System.out.println("\n=== Advanced Map Operations ===");
 90        
 91        Map<String, List<String>> studentCourses = new HashMap<>();
 92        
 93        // Adding courses for students
 94        addCourse(studentCourses, "Alice", "Math");
 95        addCourse(studentCourses, "Alice", "Science");
 96        addCourse(studentCourses, "Bob", "Math");
 97        addCourse(studentCourses, "Bob", "History");
 98        addCourse(studentCourses, "Alice", "English");
 99        
100        System.out.println("Student courses: " + studentCourses);
101        
102        // Using computeIfAbsent for cleaner code
103        Map<String, Set<String>> uniqueCourses = new HashMap<>();
104        uniqueCourses.computeIfAbsent("Alice", k -> new HashSet<>()).add("Math");
105        uniqueCourses.computeIfAbsent("Alice", k -> new HashSet<>()).add("Math");  // Duplicate
106        uniqueCourses.computeIfAbsent("Alice", k -> new HashSet<>()).add("Science");
107        
108        System.out.println("Unique courses: " + uniqueCourses);
109        
110        // Merge operation
111        Map<String, Integer> inventory1 = new HashMap<>();
112        inventory1.put("Apples", 10);
113        inventory1.put("Bananas", 15);
114        
115        Map<String, Integer> inventory2 = new HashMap<>();
116        inventory2.put("Apples", 5);
117        inventory2.put("Oranges", 20);
118        
119        // Merge inventories
120        inventory2.forEach((fruit, count) -> 
121            inventory1.merge(fruit, count, Integer::sum));
122        
123        System.out.println("Merged inventory: " + inventory1);
124        
125        // Replace operations
126        Map<String, String> cache = new HashMap<>();
127        cache.put("key1", "value1");
128        cache.put("key2", "value2");
129        
130        cache.replace("key1", "new_value1");
131        cache.replace("key3", "value3");  // Won't add since key doesn't exist
132        cache.replaceAll((key, value) -> value.toUpperCase());
133        
134        System.out.println("Cache after replace operations: " + cache);
135    }
136    
137    private static void addCourse(Map<String, List<String>> studentCourses, 
138                                  String student, String course) {
139        studentCourses.computeIfAbsent(student, k -> new ArrayList<>()).add(course);
140    }
141}

Queue and Deque Interfaces

Queues provide FIFO (First-In-First-Out) access, while deques support both FIFO and LIFO operations.

PriorityQueue and ArrayDeque

  1import java.util.*;
  2
  3public class QueueExamples {
  4    
  5    public static void demonstratePriorityQueue() {
  6        System.out.println("=== PriorityQueue Examples ===");
  7        
  8        // Natural ordering (min-heap)
  9        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
 10        minHeap.addAll(Arrays.asList(5, 2, 8, 1, 9, 3));
 11        
 12        System.out.println("Priority queue (natural order): " + minHeap);
 13        System.out.println("Polling elements in priority order:");
 14        while (!minHeap.isEmpty()) {
 15            System.out.print(minHeap.poll() + " ");
 16        }
 17        System.out.println();
 18        
 19        // Custom ordering (max-heap)
 20        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
 21        maxHeap.addAll(Arrays.asList(5, 2, 8, 1, 9, 3));
 22        
 23        System.out.println("\nPriority queue (reverse order): " + maxHeap);
 24        System.out.println("Polling elements in reverse priority order:");
 25        while (!maxHeap.isEmpty()) {
 26            System.out.print(maxHeap.poll() + " ");
 27        }
 28        System.out.println();
 29        
 30        // Priority queue with custom objects
 31        demonstrateTaskPriorityQueue();
 32    }
 33    
 34    private static void demonstrateTaskPriorityQueue() {
 35        System.out.println("\n=== Task Priority Queue ===");
 36        
 37        class Task implements Comparable<Task> {
 38            private String name;
 39            private int priority;
 40            
 41            public Task(String name, int priority) {
 42                this.name = name;
 43                this.priority = priority;
 44            }
 45            
 46            @Override
 47            public int compareTo(Task other) {
 48                return Integer.compare(this.priority, other.priority);  // Lower number = higher priority
 49            }
 50            
 51            @Override
 52            public String toString() {
 53                return String.format("%s(priority:%d)", name, priority);
 54            }
 55        }
 56        
 57        PriorityQueue<Task> taskQueue = new PriorityQueue<>();
 58        taskQueue.add(new Task("Fix bug", 1));
 59        taskQueue.add(new Task("Write docs", 3));
 60        taskQueue.add(new Task("Code review", 2));
 61        taskQueue.add(new Task("Deploy app", 1));
 62        
 63        System.out.println("Processing tasks by priority:");
 64        while (!taskQueue.isEmpty()) {
 65            Task task = taskQueue.poll();
 66            System.out.println("Processing: " + task);
 67        }
 68    }
 69    
 70    public static void demonstrateArrayDeque() {
 71        System.out.println("\n=== ArrayDeque Examples ===");
 72        
 73        Deque<String> deque = new ArrayDeque<>();
 74        
 75        // Adding elements
 76        deque.addFirst("First");
 77        deque.addLast("Last");
 78        deque.addFirst("New First");
 79        deque.addLast("New Last");
 80        
 81        System.out.println("Deque after additions: " + deque);
 82        
 83        // Accessing elements without removing
 84        String first = deque.peekFirst();
 85        String last = deque.peekLast();
 86        System.out.println("First element: " + first);
 87        System.out.println("Last element: " + last);
 88        System.out.println("Deque unchanged: " + deque);
 89        
 90        // Removing elements
 91        String removedFirst = deque.removeFirst();
 92        String removedLast = deque.removeLast();
 93        System.out.println("Removed first: " + removedFirst);
 94        System.out.println("Removed last: " + removedLast);
 95        System.out.println("Deque after removals: " + deque);
 96        
 97        // Using as stack (LIFO)
 98        System.out.println("\nUsing ArrayDeque as Stack:");
 99        Deque<Integer> stack = new ArrayDeque<>();
100        stack.push(1);
101        stack.push(2);
102        stack.push(3);
103        
104        System.out.println("Stack: " + stack);
105        while (!stack.isEmpty()) {
106            System.out.println("Popped: " + stack.pop());
107        }
108        
109        // Using as queue (FIFO)
110        System.out.println("\nUsing ArrayDeque as Queue:");
111        Queue<String> queue = new ArrayDeque<>();
112        queue.offer("Task 1");
113        queue.offer("Task 2");
114        queue.offer("Task 3");
115        
116        System.out.println("Queue: " + queue);
117        while (!queue.isEmpty()) {
118            System.out.println("Processing: " + queue.poll());
119        }
120    }
121}

Practical Collection Usage Patterns

Data Processing Examples

  1import java.util.*;
  2import java.util.stream.Collectors;
  3
  4public class CollectionPatterns {
  5    
  6    // Student class for examples
  7    static class Student {
  8        private String name;
  9        private String major;
 10        private double gpa;
 11        private List<String> courses;
 12        
 13        public Student(String name, String major, double gpa, List<String> courses) {
 14            this.name = name;
 15            this.major = major;
 16            this.gpa = gpa;
 17            this.courses = new ArrayList<>(courses);
 18        }
 19        
 20        // Getters
 21        public String getName() { return name; }
 22        public String getMajor() { return major; }
 23        public double getGpa() { return gpa; }
 24        public List<String> getCourses() { return courses; }
 25        
 26        @Override
 27        public String toString() {
 28            return String.format("Student{name='%s', major='%s', gpa=%.2f}", name, major, gpa);
 29        }
 30    }
 31    
 32    public static void demonstrateDataProcessing() {
 33        System.out.println("=== Data Processing with Collections ===");
 34        
 35        // Create sample data
 36        List<Student> students = Arrays.asList(
 37            new Student("Alice", "Computer Science", 3.8, Arrays.asList("Math", "Programming", "Algorithms")),
 38            new Student("Bob", "Mathematics", 3.5, Arrays.asList("Calculus", "Linear Algebra", "Statistics")),
 39            new Student("Charlie", "Computer Science", 3.9, Arrays.asList("Programming", "Database", "Networks")),
 40            new Student("Diana", "Physics", 3.7, Arrays.asList("Physics", "Math", "Lab")),
 41            new Student("Eve", "Computer Science", 3.6, Arrays.asList("Programming", "AI", "Machine Learning"))
 42        );
 43        
 44        // Group students by major
 45        Map<String, List<Student>> studentsByMajor = new HashMap<>();
 46        for (Student student : students) {
 47            studentsByMajor.computeIfAbsent(student.getMajor(), k -> new ArrayList<>()).add(student);
 48        }
 49        
 50        System.out.println("Students grouped by major:");
 51        studentsByMajor.forEach((major, studentList) -> {
 52            System.out.println(major + ": " + studentList.size() + " students");
 53            studentList.forEach(s -> System.out.println("  - " + s.getName()));
 54        });
 55        
 56        // Find students with high GPA
 57        List<Student> highAchievers = new ArrayList<>();
 58        for (Student student : students) {
 59            if (student.getGpa() >= 3.7) {
 60                highAchievers.add(student);
 61            }
 62        }
 63        
 64        System.out.println("\nHigh achievers (GPA >= 3.7):");
 65        highAchievers.forEach(System.out::println);
 66        
 67        // Count course enrollments
 68        Map<String, Integer> courseEnrollments = new HashMap<>();
 69        for (Student student : students) {
 70            for (String course : student.getCourses()) {
 71                courseEnrollments.merge(course, 1, Integer::sum);
 72            }
 73        }
 74        
 75        System.out.println("\nCourse enrollments:");
 76        courseEnrollments.entrySet().stream()
 77            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
 78            .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
 79        
 80        // Find students taking common courses
 81        Set<String> commonCourses = new HashSet<>(students.get(0).getCourses());
 82        for (int i = 1; i < students.size(); i++) {
 83            commonCourses.retainAll(students.get(i).getCourses());
 84        }
 85        
 86        System.out.println("\nCourses taken by all students: " + commonCourses);
 87    }
 88    
 89    public static void demonstrateCollectionAlgorithms() {
 90        System.out.println("\n=== Collection Algorithms ===");
 91        
 92        List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3, 7, 4, 6));
 93        System.out.println("Original list: " + numbers);
 94        
 95        // Sorting
 96        List<Integer> sortedNumbers = new ArrayList<>(numbers);
 97        Collections.sort(sortedNumbers);
 98        System.out.println("Sorted: " + sortedNumbers);
 99        
100        // Reverse sorting
101        List<Integer> reverseSorted = new ArrayList<>(numbers);
102        Collections.sort(reverseSorted, Collections.reverseOrder());
103        System.out.println("Reverse sorted: " + reverseSorted);
104        
105        // Shuffle
106        List<Integer> shuffled = new ArrayList<>(numbers);
107        Collections.shuffle(shuffled);
108        System.out.println("Shuffled: " + shuffled);
109        
110        // Binary search (requires sorted list)
111        int searchValue = 5;
112        int index = Collections.binarySearch(sortedNumbers, searchValue);
113        System.out.println("Index of " + searchValue + " in sorted list: " + index);
114        
115        // Min and max
116        Integer min = Collections.min(numbers);
117        Integer max = Collections.max(numbers);
118        System.out.println("Min: " + min + ", Max: " + max);
119        
120        // Frequency
121        List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana", "apple");
122        int appleCount = Collections.frequency(words, "apple");
123        System.out.println("Frequency of 'apple': " + appleCount);
124        
125        // Rotate
126        List<Integer> toRotate = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
127        Collections.rotate(toRotate, 2);
128        System.out.println("After rotating by 2: " + toRotate);
129        
130        // Fill and replace
131        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d", "e"));
132        Collections.fill(list, "x");
133        System.out.println("After fill: " + list);
134        
135        List<String> replaceList = new ArrayList<>(Arrays.asList("a", "b", "a", "c", "a"));
136        Collections.replaceAll(replaceList, "a", "z");
137        System.out.println("After replace all 'a' with 'z': " + replaceList);
138    }
139}

Performance Characteristics and Best Practices

Choosing the Right Collection

ArrayList:

  • ✅ Fast random access O(1)
  • ✅ Memory efficient
  • ❌ Slow insertion/deletion at beginning O(n)
  • 💡 Use when: frequent random access, append operations

LinkedList:

  • ✅ Fast insertion/deletion at ends O(1)
  • ✅ Implements Queue and Deque
  • ❌ Slow random access O(n)
  • ❌ Higher memory overhead
  • 💡 Use when: frequent insertion/deletion, queue operations
HashSet:
  • ✅ Fast add/remove/contains O(1) average
  • ❌ No ordering guarantee
  • 💡 Use when: uniqueness and fast lookups matter most

LinkedHashSet:

  • ✅ Maintains insertion order
  • ✅ Fast operations O(1) average
  • ❌ Higher memory overhead
  • 💡 Use when: need uniqueness and predictable iteration order

TreeSet:

  • ✅ Sorted order maintained
  • ✅ Implements NavigableSet
  • ❌ Slower operations O(log n)
  • 💡 Use when: need sorted unique elements
HashMap:
  • ✅ Fast operations O(1) average
  • ❌ No ordering guarantee
  • 💡 Use when: general-purpose key-value storage

LinkedHashMap:

  • ✅ Maintains insertion/access order
  • ✅ Fast operations O(1) average
  • 💡 Use when: need predictable iteration order

TreeMap:

  • ✅ Sorted by keys
  • ✅ Implements NavigableMap
  • ❌ Slower operations O(log n)
  • 💡 Use when: need sorted key-value pairs
### Performance Testing Example
  1import java.util.*;
  2
  3public class PerformanceComparison {
  4    
  5    public static void compareListPerformance() {
  6        System.out.println("=== List Performance Comparison ===");
  7        
  8        int size = 100000;
  9        
 10        // ArrayList vs LinkedList for different operations
 11        testAddAtEnd(size);
 12        testAddAtBeginning(size);
 13        testRandomAccess(size);
 14        testIteration(size);
 15    }
 16    
 17    private static void testAddAtEnd(int size) {
 18        System.out.println("\nAdding " + size + " elements at end:");
 19        
 20        // ArrayList
 21        long start = System.nanoTime();
 22        List<Integer> arrayList = new ArrayList<>();
 23        for (int i = 0; i < size; i++) {
 24            arrayList.add(i);
 25        }
 26        long arrayListTime = System.nanoTime() - start;
 27        
 28        // LinkedList
 29        start = System.nanoTime();
 30        List<Integer> linkedList = new LinkedList<>();
 31        for (int i = 0; i < size; i++) {
 32            linkedList.add(i);
 33        }
 34        long linkedListTime = System.nanoTime() - start;
 35        
 36        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
 37        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
 38    }
 39    
 40    private static void testAddAtBeginning(int size) {
 41        System.out.println("\nAdding " + size + " elements at beginning:");
 42        
 43        // ArrayList (expensive)
 44        long start = System.nanoTime();
 45        List<Integer> arrayList = new ArrayList<>();
 46        for (int i = 0; i < size; i++) {
 47            arrayList.add(0, i);
 48        }
 49        long arrayListTime = System.nanoTime() - start;
 50        
 51        // LinkedList (efficient)
 52        start = System.nanoTime();
 53        LinkedList<Integer> linkedList = new LinkedList<>();
 54        for (int i = 0; i < size; i++) {
 55            linkedList.addFirst(i);
 56        }
 57        long linkedListTime = System.nanoTime() - start;
 58        
 59        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
 60        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
 61    }
 62    
 63    private static void testRandomAccess(int size) {
 64        System.out.println("\nRandom access (" + size + " operations):");
 65        
 66        List<Integer> arrayList = new ArrayList<>();
 67        List<Integer> linkedList = new LinkedList<>();
 68        
 69        // Populate lists
 70        for (int i = 0; i < size; i++) {
 71            arrayList.add(i);
 72            linkedList.add(i);
 73        }
 74        
 75        Random random = new Random();
 76        
 77        // ArrayList random access
 78        long start = System.nanoTime();
 79        for (int i = 0; i < size; i++) {
 80            int index = random.nextInt(arrayList.size());
 81            arrayList.get(index);
 82        }
 83        long arrayListTime = System.nanoTime() - start;
 84        
 85        // LinkedList random access
 86        start = System.nanoTime();
 87        for (int i = 0; i < size; i++) {
 88            int index = random.nextInt(linkedList.size());
 89            linkedList.get(index);
 90        }
 91        long linkedListTime = System.nanoTime() - start;
 92        
 93        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
 94        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
 95    }
 96    
 97    private static void testIteration(int size) {
 98        System.out.println("\nIteration over " + size + " elements:");
 99        
100        List<Integer> arrayList = new ArrayList<>();
101        List<Integer> linkedList = new LinkedList<>();
102        
103        // Populate lists
104        for (int i = 0; i < size; i++) {
105            arrayList.add(i);
106            linkedList.add(i);
107        }
108        
109        // ArrayList iteration
110        long start = System.nanoTime();
111        for (Integer value : arrayList) {
112            // Simulate some work
113        }
114        long arrayListTime = System.nanoTime() - start;
115        
116        // LinkedList iteration
117        start = System.nanoTime();
118        for (Integer value : linkedList) {
119            // Simulate some work
120        }
121        long linkedListTime = System.nanoTime() - start;
122        
123        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
124        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
125    }
126}

Best Practices Summary

Collection Best Practices:

  1. Choose the right collection based on usage patterns
  2. Specify initial capacity for ArrayList/HashMap when size is known
  3. Use generics to ensure type safety
  4. Implement equals() and hashCode() properly for custom objects in Sets/Maps
  5. Use enhanced for-loops for better readability
  6. Consider using streams for data processing (Java 8+)
  7. Prefer Collections.unmodifiableX() for read-only views
  8. Use try-with-resources for collections that implement AutoCloseable

Common Pitfalls:

  1. Modifying collections during iteration (use Iterator.remove())
  2. Not overriding equals()/hashCode() for custom objects
  3. Using raw types instead of generics
  4. Choosing wrong collection type for the use case
  5. Not considering thread safety in concurrent environments

The Java Collections Framework provides powerful, flexible tools for data management. Choose the right collection for your specific needs and leverage their built-in methods for efficient data processing.


← Previous: OOP Concepts Next: File I/O Operations →