Collections Framework
Why This Matters: Collections are used in 95% of Java applications. They provide efficient data storage, searching, and manipulation. Mastering collections is essential for database operations, web development, data processing, and algorithm implementation.
Core Concept: Collections framework provides ready-made data structures so you don’t reinvent the wheel. Choose the right collection for the job.
graph TD
A[Collection Framework] --> B[List: Ordered, Duplicates OK]
A --> C[Set: No Duplicates, Unique Elements]
A --> D[Map: Key-Value Pairs]
A --> E[Queue: Processing Order]
B --> B1[ArrayList: Fast Access]
B --> B2[LinkedList: Fast Insert/Delete]
C --> C1[HashSet: Fast Lookup]
C --> C2[TreeSet: Sorted Order]
D --> D1[HashMap: Fast Key Lookup]
D --> D2[TreeMap: Sorted Keys]
E --> E1[LinkedList: FIFO Queue]
E --> E2[PriorityQueue: Priority Order]
style B fill:#e3f2fd
style C fill:#e8f5e8
style D fill:#fff3e0
style E fill:#fce4ec
Collections framework provides proven, optimized data structures for every common use case.
Collections Hierarchy Overview
/*
Collection Interface Hierarchy
Collection<E>
├── List<E> (ordered, allows duplicates)
│ ├── ArrayList<E>
│ ├── LinkedList<E>
│ └── Vector<E>
├── Set<E> (no duplicates)
│ ├── HashSet<E>
│ ├── LinkedHashSet<E>
│ └── SortedSet<E>
│ └── TreeSet<E>
└── Queue<E> (processing order)
├── LinkedList<E>
├── PriorityQueue<E>
└── Deque<E>
└── ArrayDeque<E>
Map<K,V> (key-value pairs, separate hierarchy)
├── HashMap<K,V>
├── LinkedHashMap<K,V>
└── SortedMap<K,V>
└── TreeMap<K,V>
*/
import java.util.*;
public class CollectionsOverview {
public static void demonstrateCollectionTypes() {
System.out.println("=== Collections Framework Overview ===");
// List - ordered, allows duplicates
List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");
fruits.add("apple"); // Duplicate allowed
System.out.println("List (ArrayList): " + fruits);
System.out.println("Element at index 1: " + fruits.get(1));
// Set - no duplicates
Set<String> uniqueFruits = new HashSet<>(fruits);
System.out.println("Set (HashSet): " + uniqueFruits);
System.out.println("Size after removing duplicates: " + uniqueFruits.size());
// Map - key-value pairs
Map<String, Integer> fruitCounts = new HashMap<>();
fruitCounts.put("apple", 5);
fruitCounts.put("banana", 3);
fruitCounts.put("orange", 8);
System.out.println("Map (HashMap): " + fruitCounts);
System.out.println("Apple count: " + fruitCounts.get("apple"));
// Queue - processing order
Queue<String> processingQueue = new LinkedList<>();
processingQueue.offer("task1");
processingQueue.offer("task2");
processingQueue.offer("task3");
System.out.println("Queue: " + processingQueue);
System.out.println("Next to process: " + processingQueue.peek());
// Common operations across all collections
System.out.println("\n=== Common Collection Operations ===");
Collection<String> collection = new ArrayList<>(Arrays.asList("a", "b", "c"));
System.out.println("Original: " + collection);
System.out.println("Size: " + collection.size());
System.out.println("Is empty: " + collection.isEmpty());
System.out.println("Contains 'b': " + collection.contains("b"));
collection.remove("b");
System.out.println("After removing 'b': " + collection);
collection.addAll(Arrays.asList("d", "e"));
System.out.println("After adding 'd', 'e': " + collection);
System.out.println("As array: " + Arrays.toString(collection.toArray()));
}
public static void main(String[] args) {
demonstrateCollectionTypes();
}
}
List Interface and Implementations
ArrayList - Dynamic Array Implementation
import java.util.*;
public class ArrayListDemo {
public static void demonstrateArrayListBasics() {
System.out.println("=== ArrayList Basics ===");
// Creating ArrayList
ArrayList<String> names = new ArrayList<>();
ArrayList<String> namesWithCapacity = new ArrayList<>(20); // Initial capacity
ArrayList<String> namesFromCollection = new ArrayList<>(Arrays.asList("Alice", "Bob"));
System.out.println("Empty list: " + names);
System.out.println("From collection: " + namesFromCollection);
// Adding elements
names.add("Charlie");
names.add("Diana");
names.add(0, "Aaron"); // Insert at specific index
names.addAll(Arrays.asList("Eve", "Frank"));
System.out.println("After additions: " + names);
// Accessing elements
System.out.println("First element: " + names.get(0));
System.out.println("Last element: " + names.get(names.size() - 1));
System.out.println("Index of 'Diana': " + names.indexOf("Diana"));
System.out.println("Last index of 'Charlie': " + names.lastIndexOf("Charlie"));
// Modifying elements
names.set(1, "Updated Charlie");
System.out.println("After update: " + names);
// Removing elements
names.remove("Eve"); // Remove by object
names.remove(0); // Remove by index
System.out.println("After removals: " + names);
}
public static void demonstrateArrayListIteration() {
System.out.println("\n=== ArrayList Iteration Methods ===");
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// 1. Traditional for loop
System.out.print("Traditional for: ");
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i) + " ");
}
System.out.println();
// 2. Enhanced for loop (for-each)
System.out.print("Enhanced for: ");
for (Integer num : numbers) {
System.out.print(num + " ");
}
System.out.println();
// 3. Iterator
System.out.print("Iterator: ");
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.println();
// 4. ListIterator (bidirectional)
System.out.print("ListIterator (reverse): ");
ListIterator<Integer> listIterator = numbers.listIterator(numbers.size());
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + " ");
}
System.out.println();
// 5. Stream API
System.out.print("Stream API: ");
numbers.stream().forEach(num -> System.out.print(num + " "));
System.out.println();
// 6. Method reference
System.out.print("Method reference: ");
numbers.forEach(System.out::print);
numbers.forEach(num -> System.out.print(" "));
System.out.println();
}
public static void demonstrateArrayListOperations() {
System.out.println("\n=== ArrayList Advanced Operations ===");
ArrayList<String> words = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date", "elderberry"));
// Sublist operations
List<String> subWords = words.subList(1, 4);
System.out.println("Original: " + words);
System.out.println("Sublist [1,4): " + subWords);
// Modifying sublist affects original
subWords.set(0, "BANANA");
System.out.println("After sublist modification: " + words);
// Sorting
ArrayList<String> sortedWords = new ArrayList<>(words);
Collections.sort(sortedWords);
System.out.println("Sorted: " + sortedWords);
// Reverse sorting
Collections.sort(sortedWords, Collections.reverseOrder());
System.out.println("Reverse sorted: " + sortedWords);
// Searching (binary search requires sorted list)
Collections.sort(words);
int index = Collections.binarySearch(words, "cherry");
System.out.println("Binary search for 'cherry': " + index);
// Shuffling
Collections.shuffle(words);
System.out.println("Shuffled: " + words);
// Converting to array
String[] wordArray = words.toArray(new String[0]);
System.out.println("As array: " + Arrays.toString(wordArray));
}
// Performance characteristics demo
public static void demonstrateArrayListPerformance() {
System.out.println("\n=== ArrayList Performance Characteristics ===");
ArrayList<Integer> list = new ArrayList<>();
// Adding elements - O(1) amortized
long startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long addTime = System.nanoTime() - startTime;
// Random access - O(1)
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
int randomIndex = (int) (Math.random() * list.size());
list.get(randomIndex);
}
long accessTime = System.nanoTime() - startTime;
// Insertion at beginning - O(n)
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
list.add(0, i);
}
long insertTime = System.nanoTime() - startTime;
System.out.printf("Add 100k elements: %.2f ms%n", addTime / 1_000_000.0);
System.out.printf("10k random access: %.2f ms%n", accessTime / 1_000_000.0);
System.out.printf("1k insertions at start: %.2f ms%n", insertTime / 1_000_000.0);
}
public static void main(String[] args) {
demonstrateArrayListBasics();
demonstrateArrayListIteration();
demonstrateArrayListOperations();
demonstrateArrayListPerformance();
}
}
LinkedList - Doubly Linked List Implementation
import java.util.*;
public class LinkedListDemo {
public static void demonstrateLinkedListBasics() {
System.out.println("=== LinkedList Basics ===");
LinkedList<String> linkedList = new LinkedList<>();
// LinkedList-specific methods
linkedList.addFirst("First");
linkedList.addLast("Last");
linkedList.add("Middle");
System.out.println("LinkedList: " + linkedList);
// Access first and last elements
System.out.println("First element: " + linkedList.getFirst());
System.out.println("Last element: " + linkedList.getLast());
System.out.println("Peek first: " + linkedList.peekFirst());
System.out.println("Peek last: " + linkedList.peekLast());
// Remove first and last elements
String removedFirst = linkedList.removeFirst();
String removedLast = linkedList.removeLast();
System.out.println("Removed first: " + removedFirst);
System.out.println("Removed last: " + removedLast);
System.out.println("After removals: " + linkedList);
}
public static void demonstrateLinkedListAsQueue() {
System.out.println("\n=== LinkedList as Queue ===");
Queue<String> queue = new LinkedList<>();
// Enqueue operations
queue.offer("Task 1");
queue.offer("Task 2");
queue.offer("Task 3");
System.out.println("Queue: " + queue);
// Process queue
while (!queue.isEmpty()) {
String task = queue.poll();
System.out.println("Processing: " + task);
}
System.out.println("Queue after processing: " + queue);
}
public static void demonstrateLinkedListAsDeque() {
System.out.println("\n=== LinkedList as Deque (Double-ended Queue) ===");
Deque<Integer> deque = new LinkedList<>();
// Add to both ends
deque.addFirst(2);
deque.addLast(3);
deque.addFirst(1);
deque.addLast(4);
System.out.println("Deque: " + deque);
// Remove from both ends
System.out.println("Remove first: " + deque.removeFirst());
System.out.println("Remove last: " + deque.removeLast());
System.out.println("Deque after removals: " + deque);
// Use as stack (LIFO)
System.out.println("\nUsing as Stack:");
Deque<String> stack = new LinkedList<>();
stack.push("Bottom");
stack.push("Middle");
stack.push("Top");
System.out.println("Stack: " + stack);
while (!stack.isEmpty()) {
System.out.println("Pop: " + stack.pop());
}
}
// Performance comparison with ArrayList
public static void compareWithArrayList() {
System.out.println("\n=== LinkedList vs ArrayList Performance ===");
int size = 100000;
// Test 1: Adding elements at the end
long startTime = System.nanoTime();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
}
long arrayListAddTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) {
linkedList.add(i);
}
long linkedListAddTime = System.nanoTime() - startTime;
// Test 2: Random access
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
int randomIndex = (int) (Math.random() * arrayList.size());
arrayList.get(randomIndex);
}
long arrayListAccessTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
int randomIndex = (int) (Math.random() * linkedList.size());
linkedList.get(randomIndex);
}
long linkedListAccessTime = System.nanoTime() - startTime;
// Test 3: Insertion at beginning
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
arrayList.add(0, i);
}
long arrayListInsertTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
linkedList.add(0, i);
}
long linkedListInsertTime = System.nanoTime() - startTime;
System.out.println("Performance Comparison (times in ms):");
System.out.printf("%-20s %-10s %-10s%n", "Operation", "ArrayList", "LinkedList");
System.out.printf("%-20s %-10.2f %-10.2f%n", "Add " + size + " elements",
arrayListAddTime / 1_000_000.0, linkedListAddTime / 1_000_000.0);
System.out.printf("%-20s %-10.2f %-10.2f%n", "10k random access",
arrayListAccessTime / 1_000_000.0, linkedListAccessTime / 1_000_000.0);
System.out.printf("%-20s %-10.2f %-10.2f%n", "1k insert at start",
arrayListInsertTime / 1_000_000.0, linkedListInsertTime / 1_000_000.0);
}
public static void main(String[] args) {
demonstrateLinkedListBasics();
demonstrateLinkedListAsQueue();
demonstrateLinkedListAsDeque();
compareWithArrayList();
}
}
Set Interface and Implementations
HashSet, LinkedHashSet, and TreeSet
import java.util.*;
public class SetDemo {
public static void demonstrateHashSet() {
System.out.println("=== HashSet Demo ===");
// HashSet - no duplicates, no guaranteed order
HashSet<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("cherry");
hashSet.add("apple"); // Duplicate - won't be added
System.out.println("HashSet: " + hashSet);
System.out.println("Size: " + hashSet.size());
System.out.println("Contains 'banana': " + hashSet.contains("banana"));
// Adding collection
hashSet.addAll(Arrays.asList("date", "elderberry", "fig"));
System.out.println("After adding collection: " + hashSet);
// Remove elements
hashSet.remove("apple");
System.out.println("After removing 'apple': " + hashSet);
// Iteration (order not guaranteed)
System.out.print("Iteration order: ");
for (String fruit : hashSet) {
System.out.print(fruit + " ");
}
System.out.println();
}
public static void demonstrateLinkedHashSet() {
System.out.println("\n=== LinkedHashSet Demo ===");
// LinkedHashSet - no duplicates, maintains insertion order
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("first");
linkedHashSet.add("second");
linkedHashSet.add("third");
linkedHashSet.add("first"); // Duplicate - won't be added
linkedHashSet.add("fourth");
System.out.println("LinkedHashSet: " + linkedHashSet);
// Maintains insertion order
System.out.print("Insertion order maintained: ");
for (String item : linkedHashSet) {
System.out.print(item + " ");
}
System.out.println();
// Performance comparison
Set<Integer> hashSet = new HashSet<>();
Set<Integer> linkedHashSet2 = new LinkedHashSet<>();
long startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
hashSet.add(i);
}
long hashSetTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 100000; i++) {
linkedHashSet2.add(i);
}
long linkedHashSetTime = System.nanoTime() - startTime;
System.out.printf("HashSet add time: %.2f ms%n", hashSetTime / 1_000_000.0);
System.out.printf("LinkedHashSet add time: %.2f ms%n", linkedHashSetTime / 1_000_000.0);
}
public static void demonstrateTreeSet() {
System.out.println("\n=== TreeSet Demo ===");
// TreeSet - no duplicates, maintains sorted order
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("zebra");
treeSet.add("apple");
treeSet.add("monkey");
treeSet.add("banana");
treeSet.add("apple"); // Duplicate - won't be added
System.out.println("TreeSet (sorted): " + treeSet);
// TreeSet specific methods
System.out.println("First element: " + treeSet.first());
System.out.println("Last element: " + treeSet.last());
System.out.println("Lower than 'monkey': " + treeSet.lower("monkey"));
System.out.println("Higher than 'banana': " + treeSet.higher("banana"));
// Subset operations
SortedSet<String> headSet = treeSet.headSet("monkey");
SortedSet<String> tailSet = treeSet.tailSet("monkey");
SortedSet<String> subSet = treeSet.subSet("banana", "zebra");
System.out.println("Head set (< 'monkey'): " + headSet);
System.out.println("Tail set (>= 'monkey'): " + tailSet);
System.out.println("Sub set ['banana', 'zebra'): " + subSet);
// Custom sorting with Comparator
TreeSet<String> lengthSortedSet = new TreeSet<>(Comparator.comparing(String::length));
lengthSortedSet.addAll(Arrays.asList("a", "bb", "ccc", "dddd", "ee"));
System.out.println("Sorted by length: " + lengthSortedSet);
// Reverse order
TreeSet<Integer> reverseSet = new TreeSet<>(Collections.reverseOrder());
reverseSet.addAll(Arrays.asList(5, 2, 8, 1, 9, 3));
System.out.println("Reverse order: " + reverseSet);
}
public static void demonstrateSetOperations() {
System.out.println("\n=== Set Operations (Mathematical) ===");
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
System.out.println("Set 1: " + set1);
System.out.println("Set 2: " + set2);
// Union
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("Union (set1 ∪ set2): " + union);
// Intersection
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("Intersection (set1 ∩ set2): " + intersection);
// Difference
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("Difference (set1 - set2): " + difference);
// Symmetric difference
Set<Integer> symmetricDiff = new HashSet<>(union);
symmetricDiff.removeAll(intersection);
System.out.println("Symmetric difference: " + symmetricDiff);
// Subset check
Set<Integer> subset = new HashSet<>(Arrays.asList(2, 3));
System.out.println("Is {2, 3} subset of set1: " + set1.containsAll(subset));
}
// Custom object in Set
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Important: Override equals and hashCode for proper Set behavior
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
// For TreeSet natural ordering
public String getName() { return name; }
public int getAge() { return age; }
}
public static void demonstrateCustomObjectsInSet() {
System.out.println("\n=== Custom Objects in Set ===");
Set<Person> people = new HashSet<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Alice", 25)); // Duplicate based on equals()
people.add(new Person("Charlie", 35));
System.out.println("HashSet of people: " + people);
System.out.println("Size: " + people.size()); // Should be 3, not 4
// TreeSet with custom comparator
Set<Person> sortedPeople = new TreeSet<>(Comparator.comparing(Person::getName));
sortedPeople.addAll(people);
System.out.println("TreeSet sorted by name: " + sortedPeople);
// TreeSet sorted by age
Set<Person> ageSortedPeople = new TreeSet<>(Comparator.comparingInt(Person::getAge));
ageSortedPeople.addAll(people);
System.out.println("TreeSet sorted by age: " + ageSortedPeople);
}
public static void main(String[] args) {
demonstrateHashSet();
demonstrateLinkedHashSet();
demonstrateTreeSet();
demonstrateSetOperations();
demonstrateCustomObjectsInSet();
}
}
Map Interface and Implementations
HashMap, LinkedHashMap, and TreeMap
import java.util.*;
public class MapDemo {
public static void demonstrateHashMap() {
System.out.println("=== HashMap Demo ===");
// HashMap - key-value pairs, no guaranteed order
HashMap<String, Integer> scores = new HashMap<>();
// Adding entries
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
scores.put("Diana", 98);
System.out.println("HashMap: " + scores);
// Accessing values
System.out.println("Alice's score: " + scores.get("Alice"));
System.out.println("Eve's score: " + scores.get("Eve")); // null for non-existent key
System.out.println("Eve's score (with default): " + scores.getOrDefault("Eve", 0));
// Checking keys and values
System.out.println("Contains key 'Bob': " + scores.containsKey("Bob"));
System.out.println("Contains value 95: " + scores.containsValue(95));
// Updating values
scores.put("Alice", 97); // Overwrites existing value
scores.putIfAbsent("Eve", 85); // Only adds if key doesn't exist
System.out.println("After updates: " + scores);
// Removing entries
scores.remove("Bob");
boolean removed = scores.remove("Charlie", 92); // Remove only if key-value pair matches
System.out.println("Removed Charlie with score 92: " + removed);
System.out.println("After removals: " + scores);
// Compute operations (Java 8+)
scores.compute("Alice", (key, value) -> value + 5); // Alice: 97 -> 102
scores.computeIfAbsent("Frank", key -> 80); // Add Frank if not present
scores.computeIfPresent("Diana", (key, value) -> value + 2); // Diana: 98 -> 100
System.out.println("After compute operations: " + scores);
// Merge operation
scores.merge("Alice", 10, Integer::sum); // Alice: 102 + 10 = 112
System.out.println("After merge: " + scores);
}
public static void demonstrateMapIteration() {
System.out.println("\n=== Map Iteration Methods ===");
Map<String, Integer> map = new HashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 8);
// 1. Iterate over keys
System.out.println("Keys:");
for (String key : map.keySet()) {
System.out.println(" " + key + " -> " + map.get(key));
}
// 2. Iterate over values
System.out.println("Values:");
for (Integer value : map.values()) {
System.out.println(" " + value);
}
// 3. Iterate over entries
System.out.println("Entries:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(" " + entry.getKey() + " = " + entry.getValue());
}
// 4. Java 8 forEach
System.out.println("forEach with lambda:");
map.forEach((key, value) -> System.out.println(" " + key + ": " + value));
// 5. Stream API
System.out.println("Stream API:");
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(entry -> System.out.println(" " + entry.getKey() + " = " + entry.getValue()));
}
public static void demonstrateLinkedHashMap() {
System.out.println("\n=== LinkedHashMap Demo ===");
// LinkedHashMap - maintains insertion order
LinkedHashMap<String, String> insertionOrder = new LinkedHashMap<>();
insertionOrder.put("first", "1st");
insertionOrder.put("third", "3rd");
insertionOrder.put("second", "2nd");
insertionOrder.put("fourth", "4th");
System.out.println("LinkedHashMap (insertion order): " + insertionOrder);
// LinkedHashMap with access order (LRU cache behavior)
LinkedHashMap<String, String> accessOrder = new LinkedHashMap<>(16, 0.75f, true);
accessOrder.put("A", "First");
accessOrder.put("B", "Second");
accessOrder.put("C", "Third");
System.out.println("Before access: " + accessOrder);
accessOrder.get("A"); // Access A, moves it to end
System.out.println("After accessing A: " + accessOrder);
// LRU Cache implementation
LRUCache<String, String> lruCache = new LRUCache<>(3);
lruCache.put("1", "One");
lruCache.put("2", "Two");
lruCache.put("3", "Three");
System.out.println("LRU Cache: " + lruCache);
lruCache.put("4", "Four"); // Should evict oldest entry
System.out.println("After adding 4th item: " + lruCache);
}
// Simple LRU Cache implementation
static class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(16, 0.75f, true); // access-order
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
public static void demonstrateTreeMap() {
System.out.println("\n=== TreeMap Demo ===");
// TreeMap - sorted by keys
TreeMap<String, Double> prices = new TreeMap<>();
prices.put("Apple", 1.50);
prices.put("Banana", 0.80);
prices.put("Cherry", 3.20);
prices.put("Date", 2.10);
System.out.println("TreeMap (sorted by key): " + prices);
// TreeMap specific methods
System.out.println("First key: " + prices.firstKey());
System.out.println("Last key: " + prices.lastKey());
System.out.println("Lower key than 'Cherry': " + prices.lowerKey("Cherry"));
System.out.println("Higher key than 'Banana': " + prices.higherKey("Banana"));
// Subset operations
SortedMap<String, Double> headMap = prices.headMap("Cherry");
SortedMap<String, Double> tailMap = prices.tailMap("Cherry");
SortedMap<String, Double> subMap = prices.subMap("Banana", "Date");
System.out.println("Head map (< 'Cherry'): " + headMap);
System.out.println("Tail map (>= 'Cherry'): " + tailMap);
System.out.println("Sub map ['Banana', 'Date'): " + subMap);
// Custom comparator (reverse order)
TreeMap<String, Double> reversePrices = new TreeMap<>(Collections.reverseOrder());
reversePrices.putAll(prices);
System.out.println("Reverse order TreeMap: " + reversePrices);
// TreeMap with custom objects
TreeMap<Person, String> personRoles = new TreeMap<>(Comparator.comparing(Person::getName));
personRoles.put(new Person("Alice", 30), "Manager");
personRoles.put(new Person("Bob", 25), "Developer");
personRoles.put(new Person("Charlie", 35), "Architect");
System.out.println("TreeMap with custom objects: " + personRoles);
}
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + "(" + age + ")";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public static void demonstrateMapPerformance() {
System.out.println("\n=== Map Performance Comparison ===");
int size = 100000;
// HashMap performance
long startTime = System.nanoTime();
Map<Integer, String> hashMap = new HashMap<>();
for (int i = 0; i < size; i++) {
hashMap.put(i, "value" + i);
}
long hashMapPutTime = System.nanoTime() - startTime;
// TreeMap performance
startTime = System.nanoTime();
Map<Integer, String> treeMap = new TreeMap<>();
for (int i = 0; i < size; i++) {
treeMap.put(i, "value" + i);
}
long treeMapPutTime = System.nanoTime() - startTime;
// Get performance
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
hashMap.get(i);
}
long hashMapGetTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
treeMap.get(i);
}
long treeMapGetTime = System.nanoTime() - startTime;
System.out.printf("Performance (times in ms):%n");
System.out.printf("%-15s %-10s %-10s%n", "Operation", "HashMap", "TreeMap");
System.out.printf("%-15s %-10.2f %-10.2f%n", "Put " + size,
hashMapPutTime / 1_000_000.0, treeMapPutTime / 1_000_000.0);
System.out.printf("%-15s %-10.2f %-10.2f%n", "Get 10k",
hashMapGetTime / 1_000_000.0, treeMapGetTime / 1_000_000.0);
}
public static void main(String[] args) {
demonstrateHashMap();
demonstrateMapIteration();
demonstrateLinkedHashMap();
demonstrateTreeMap();
demonstrateMapPerformance();
}
}
Advanced Collection Operations
Utility Classes and Algorithms
import java.util.*;
public class CollectionUtilities {
public static void demonstrateCollectionsUtilities() {
System.out.println("=== Collections Utility Class ===");
List<String> names = new ArrayList<>(Arrays.asList("Charlie", "Alice", "Bob", "Diana"));
System.out.println("Original list: " + names);
// Sorting
Collections.sort(names);
System.out.println("Sorted: " + names);
Collections.sort(names, Collections.reverseOrder());
System.out.println("Reverse sorted: " + names);
Collections.sort(names, Comparator.comparing(String::length));
System.out.println("Sorted by length: " + names);
// Shuffling
Collections.shuffle(names);
System.out.println("Shuffled: " + names);
// Searching (requires sorted list)
Collections.sort(names);
int index = Collections.binarySearch(names, "Bob");
System.out.println("Binary search for 'Bob': " + index);
// Min and Max
System.out.println("Min: " + Collections.min(names));
System.out.println("Max: " + Collections.max(names));
System.out.println("Min by length: " + Collections.min(names, Comparator.comparing(String::length)));
// Frequency
names.add("Alice"); // Add duplicate
System.out.println("Frequency of 'Alice': " + Collections.frequency(names, "Alice"));
// Rotating
Collections.rotate(names, 2);
System.out.println("Rotated by 2: " + names);
// Filling
List<String> fillList = new ArrayList<>(Collections.nCopies(5, "default"));
System.out.println("Filled list: " + fillList);
Collections.fill(fillList, "updated");
System.out.println("After fill: " + fillList);
// Reversing
Collections.reverse(names);
System.out.println("Reversed: " + names);
}
public static void demonstrateArraysUtilities() {
System.out.println("\n=== Arrays Utility Class ===");
// Array creation and manipulation
int[] numbers = {5, 2, 8, 1, 9, 3};
System.out.println("Original array: " + Arrays.toString(numbers));
// Sorting
int[] sortedNumbers = Arrays.copyOf(numbers, numbers.length);
Arrays.sort(sortedNumbers);
System.out.println("Sorted: " + Arrays.toString(sortedNumbers));
// Partial sorting
int[] partialSort = Arrays.copyOf(numbers, numbers.length);
Arrays.sort(partialSort, 1, 4); // Sort elements at indices 1-3
System.out.println("Partial sort [1,4): " + Arrays.toString(partialSort));
// Binary search
int searchIndex = Arrays.binarySearch(sortedNumbers, 5);
System.out.println("Binary search for 5: " + searchIndex);
// Filling
int[] fillArray = new int[5];
Arrays.fill(fillArray, 42);
System.out.println("Filled array: " + Arrays.toString(fillArray));
// Equality
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
int[] array3 = {1, 2, 4};
System.out.println("Arrays equal (array1, array2): " + Arrays.equals(array1, array2));
System.out.println("Arrays equal (array1, array3): " + Arrays.equals(array1, array3));
// Convert array to list
String[] stringArray = {"a", "b", "c"};
List<String> listFromArray = Arrays.asList(stringArray);
System.out.println("List from array: " + listFromArray);
// Note: Arrays.asList returns fixed-size list
try {
listFromArray.add("d"); // This will throw UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("Cannot add to Arrays.asList result: " + e.getMessage());
}
// To get modifiable list
List<String> modifiableList = new ArrayList<>(Arrays.asList(stringArray));
modifiableList.add("d");
System.out.println("Modifiable list: " + modifiableList);
}
public static void demonstrateImmutableCollections() {
System.out.println("\n=== Immutable Collections ===");
// Java 9+ factory methods
List<String> immutableList = List.of("a", "b", "c");
Set<String> immutableSet = Set.of("x", "y", "z");
Map<String, Integer> immutableMap = Map.of("one", 1, "two", 2, "three", 3);
System.out.println("Immutable list: " + immutableList);
System.out.println("Immutable set: " + immutableSet);
System.out.println("Immutable map: " + immutableMap);
// These collections are truly immutable
try {
immutableList.add("d");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify immutable list: " + e.getMessage());
}
// Collections.unmodifiableXxx() methods (Java 1.2+)
List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> unmodifiableView = Collections.unmodifiableList(mutableList);
System.out.println("Unmodifiable view: " + unmodifiableView);
// The view reflects changes to the original
mutableList.add("d");
System.out.println("After modifying original: " + unmodifiableView);
// But the view itself cannot be modified
try {
unmodifiableView.add("e");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify unmodifiable view: " + e.getMessage());
}
// Empty collections
List<String> emptyList = Collections.emptyList();
Set<String> emptySet = Collections.emptySet();
Map<String, String> emptyMap = Collections.emptyMap();
System.out.println("Empty collections - List: " + emptyList + ", Set: " + emptySet + ", Map: " + emptyMap);
// Singleton collections
List<String> singletonList = Collections.singletonList("only");
Set<String> singletonSet = Collections.singleton("only");
Map<String, String> singletonMap = Collections.singletonMap("key", "value");
System.out.println("Singleton collections - List: " + singletonList + ", Set: " + singletonSet + ", Map: " + singletonMap);
}
public static void demonstrateSynchronizedCollections() {
System.out.println("\n=== Synchronized Collections ===");
// Create synchronized collections
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// Add some data
syncList.addAll(Arrays.asList("a", "b", "c"));
syncSet.addAll(Arrays.asList("x", "y", "z"));
syncMap.put("key1", "value1");
syncMap.put("key2", "value2");
System.out.println("Synchronized list: " + syncList);
System.out.println("Synchronized set: " + syncSet);
System.out.println("Synchronized map: " + syncMap);
// Important: Iteration still requires external synchronization
synchronized (syncList) {
Iterator<String> iterator = syncList.iterator();
while (iterator.hasNext()) {
System.out.println("Synchronized iteration: " + iterator.next());
}
}
// Modern approach: Use concurrent collections instead
List<String> concurrentList = new ArrayList<>(); // For lists, use explicit locking or CopyOnWriteArrayList
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();
Map<String, String> concurrentMap = new java.util.concurrent.ConcurrentHashMap<>();
System.out.println("Concurrent collections are generally preferred for thread-safety");
}
public static void main(String[] args) {
demonstrateCollectionsUtilities();
demonstrateArraysUtilities();
demonstrateImmutableCollections();
demonstrateSynchronizedCollections();
}
}
Collection Performance and Best Practices
Choosing the Right Collection
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class CollectionBestPractices {
public static void demonstrateCollectionChoiceGuidelines() {
System.out.println("=== Collection Choice Guidelines ===");
System.out.println("List implementations:");
System.out.println("- ArrayList: Best for random access, iteration, and when you add mostly at the end");
System.out.println("- LinkedList: Best for frequent insertion/deletion in the middle, use as Queue/Deque");
System.out.println("- Vector: Legacy, synchronized, generally avoid in favor of ArrayList");
System.out.println("\nSet implementations:");
System.out.println("- HashSet: Best general-purpose Set, O(1) operations");
System.out.println("- LinkedHashSet: When you need insertion order preservation");
System.out.println("- TreeSet: When you need sorted elements and navigation operations");
System.out.println("\nMap implementations:");
System.out.println("- HashMap: Best general-purpose Map, O(1) operations");
System.out.println("- LinkedHashMap: When you need insertion/access order preservation");
System.out.println("- TreeMap: When you need sorted keys and navigation operations");
System.out.println("- ConcurrentHashMap: Thread-safe alternative to HashMap");
}
public static void demonstratePerformanceConsiderations() {
System.out.println("\n=== Performance Considerations ===");
// Initial capacity matters for HashMap/HashSet
long startTime = System.nanoTime();
Map<Integer, String> defaultCapacityMap = new HashMap<>();
for (int i = 0; i < 100000; i++) {
defaultCapacityMap.put(i, "value" + i);
}
long defaultTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
Map<Integer, String> properCapacityMap = new HashMap<>(150000); // Avoid resizing
for (int i = 0; i < 100000; i++) {
properCapacityMap.put(i, "value" + i);
}
long properTime = System.nanoTime() - startTime;
System.out.printf("HashMap with default capacity: %.2f ms%n", defaultTime / 1_000_000.0);
System.out.printf("HashMap with proper capacity: %.2f ms%n", properTime / 1_000_000.0);
System.out.printf("Performance improvement: %.1fx%n", (double) defaultTime / properTime);
// ArrayList vs LinkedList for different operations
demonstrateListPerformance();
}
private static void demonstrateListPerformance() {
System.out.println("\n--- List Performance Comparison ---");
int size = 50000;
// ArrayList vs LinkedList for adding at end
long startTime = System.nanoTime();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
}
long arrayListAddTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) {
linkedList.add(i);
}
long linkedListAddTime = System.nanoTime() - startTime;
// Random access performance
Random random = new Random();
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
arrayList.get(random.nextInt(arrayList.size()));
}
long arrayListGetTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
linkedList.get(random.nextInt(linkedList.size()));
}
long linkedListGetTime = System.nanoTime() - startTime;
System.out.printf("Adding %d elements:%n", size);
System.out.printf(" ArrayList: %.2f ms%n", arrayListAddTime / 1_000_000.0);
System.out.printf(" LinkedList: %.2f ms%n", linkedListAddTime / 1_000_000.0);
System.out.printf("10k random access:%n");
System.out.printf(" ArrayList: %.2f ms%n", arrayListGetTime / 1_000_000.0);
System.out.printf(" LinkedList: %.2f ms%n", linkedListGetTime / 1_000_000.0);
}
public static void demonstrateCommonPitfalls() {
System.out.println("\n=== Common Pitfalls and Solutions ===");
// Pitfall 1: Modifying collection during iteration
System.out.println("1. Modifying collection during iteration:");
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// ❌ BAD: This will throw ConcurrentModificationException
try {
for (String item : list) {
if (item.equals("b")) {
list.remove(item); // Don't do this!
}
}
} catch (ConcurrentModificationException e) {
System.out.println(" ConcurrentModificationException caught!");
}
// ✅ GOOD: Use Iterator.remove()
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("b")) {
iterator.remove(); // Safe removal
}
}
System.out.println(" After safe removal: " + list);
// ✅ BETTER: Use removeIf (Java 8+)
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
list.removeIf(item -> item.equals("b"));
System.out.println(" Using removeIf: " + list);
// Pitfall 2: Using == instead of equals() for objects
System.out.println("\n2. Object comparison in collections:");
List<String> stringList = new ArrayList<>();
stringList.add(new String("hello"));
stringList.add("world");
String searchString = new String("hello");
System.out.println(" Contains 'hello': " + stringList.contains(searchString)); // Uses equals()
// Pitfall 3: Not overriding equals() and hashCode()
System.out.println("\n3. Custom objects without proper equals/hashCode:");
Set<BadPerson> badSet = new HashSet<>();
badSet.add(new BadPerson("John"));
badSet.add(new BadPerson("John")); // Should be same person, but will be added as different
System.out.println(" Bad person set size: " + badSet.size()); // Will be 2, not 1
Set<GoodPerson> goodSet = new HashSet<>();
goodSet.add(new GoodPerson("John"));
goodSet.add(new GoodPerson("John")); // Properly treated as same person
System.out.println(" Good person set size: " + goodSet.size()); // Will be 1
// Pitfall 4: Using raw types
System.out.println("\n4. Raw types vs parameterized types:");
// ❌ BAD: Raw type
@SuppressWarnings("rawtypes")
List rawList = new ArrayList();
rawList.add("string");
rawList.add(123); // No compile-time type checking
// ✅ GOOD: Parameterized type
List<String> typedList = new ArrayList<>();
typedList.add("string");
// typedList.add(123); // Compile error - type safety
System.out.println(" Always use parameterized types for type safety");
}
// Bad example - no equals/hashCode override
static class BadPerson {
private String name;
public BadPerson(String name) {
this.name = name;
}
@Override
public String toString() {
return "BadPerson{name='" + name + "'}";
}
}
// Good example - proper equals/hashCode override
static class GoodPerson {
private String name;
public GoodPerson(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
GoodPerson that = (GoodPerson) obj;
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "GoodPerson{name='" + name + "'}";
}
}
public static void demonstrateMemoryEfficiency() {
System.out.println("\n=== Memory Efficiency Tips ===");
// 1. Use ArrayList.trimToSize() after bulk operations
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
System.out.println("ArrayList capacity before trim: estimated ~" + (list.size() * 1.5));
list.trimToSize();
System.out.println("ArrayList capacity after trim: " + list.size());
// 2. Use EnumSet for enum collections
EnumSet<Day> weekdays = EnumSet.of(Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY, Day.THURSDAY, Day.FRIDAY);
System.out.println("EnumSet is more memory efficient than HashSet for enums: " + weekdays);
// 3. Consider primitive collections for performance-critical code
System.out.println("Consider primitive collections (e.g., Eclipse Collections, Trove) for better performance");
// 4. Use Stream operations for complex transformations
List<String> words = Arrays.asList("hello", "world", "java", "collections");
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.filter(word -> word.length() > 4)
.sorted()
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("Stream processed words: " + upperCaseWords);
}
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public static void main(String[] args) {
demonstrateCollectionChoiceGuidelines();
demonstratePerformanceConsiderations();
demonstrateCommonPitfalls();
demonstrateMemoryEfficiency();
}
}
Practical Collections Applications
Real-World Example: Student Management System
import java.util.*;
import java.util.stream.Collectors;
public class StudentManagementSystem {
// Student class with proper equals/hashCode
static class Student {
private final String studentId;
private final String name;
private final int age;
private final String major;
private final Map<String, Double> grades;
public Student(String studentId, String name, int age, String major) {
this.studentId = studentId;
this.name = name;
this.age = age;
this.major = major;
this.grades = new HashMap<>();
}
public void addGrade(String course, double grade) {
grades.put(course, grade);
}
public double getGPA() {
if (grades.isEmpty()) return 0.0;
return grades.values().stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
}
// Getters
public String getStudentId() { return studentId; }
public String getName() { return name; }
public int getAge() { return age; }
public String getMajor() { return major; }
public Map<String, Double> getGrades() { return new HashMap<>(grades); }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return Objects.equals(studentId, student.studentId);
}
@Override
public int hashCode() {
return Objects.hash(studentId);
}
@Override
public String toString() {
return String.format("Student{id='%s', name='%s', age=%d, major='%s', GPA=%.2f}",
studentId, name, age, major, getGPA());
}
}
// Course class
static class Course {
private final String courseId;
private final String courseName;
private final int credits;
private final Set<String> enrolledStudents;
public Course(String courseId, String courseName, int credits) {
this.courseId = courseId;
this.courseName = courseName;
this.credits = credits;
this.enrolledStudents = new HashSet<>();
}
public void enrollStudent(String studentId) {
enrolledStudents.add(studentId);
}
public void dropStudent(String studentId) {
enrolledStudents.remove(studentId);
}
// Getters
public String getCourseId() { return courseId; }
public String getCourseName() { return courseName; }
public int getCredits() { return credits; }
public Set<String> getEnrolledStudents() { return new HashSet<>(enrolledStudents); }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Course course = (Course) obj;
return Objects.equals(courseId, course.courseId);
}
@Override
public int hashCode() {
return Objects.hash(courseId);
}
@Override
public String toString() {
return String.format("Course{id='%s', name='%s', credits=%d, enrolled=%d}",
courseId, courseName, credits, enrolledStudents.size());
}
}
// Main system class
private final Map<String, Student> students; // studentId -> Student
private final Map<String, Course> courses; // courseId -> Course
private final Map<String, List<String>> majorToStudents; // major -> List of studentIds
private final TreeMap<Double, List<Student>> gpaRanking; // GPA -> Students (sorted)
public StudentManagementSystem() {
this.students = new HashMap<>();
this.courses = new HashMap<>();
this.majorToStudents = new HashMap<>();
this.gpaRanking = new TreeMap<>(Collections.reverseOrder()); // Highest GPA first
}
// Add student
public void addStudent(Student student) {
students.put(student.getStudentId(), student);
// Update major index
majorToStudents.computeIfAbsent(student.getMajor(), k -> new ArrayList<>())
.add(student.getStudentId());
updateGPARanking(student);
System.out.println("Added student: " + student);
}
// Add course
public void addCourse(Course course) {
courses.put(course.getCourseId(), course);
System.out.println("Added course: " + course);
}
// Enroll student in course
public boolean enrollStudent(String studentId, String courseId) {
Student student = students.get(studentId);
Course course = courses.get(courseId);
if (student == null || course == null) {
System.out.println("Student or course not found");
return false;
}
course.enrollStudent(studentId);
System.out.printf("Enrolled %s in %s%n", student.getName(), course.getCourseName());
return true;
}
// Add grade for student
public void addGrade(String studentId, String courseId, double grade) {
Student student = students.get(studentId);
Course course = courses.get(courseId);
if (student == null || course == null) {
System.out.println("Student or course not found");
return;
}
if (!course.getEnrolledStudents().contains(studentId)) {
System.out.println("Student is not enrolled in this course");
return;
}
// Remove from old GPA ranking
removeFromGPARanking(student);
// Add grade
student.addGrade(courseId, grade);
// Update GPA ranking
updateGPARanking(student);
System.out.printf("Added grade %.2f for %s in %s%n",
grade, student.getName(), course.getCourseName());
}
private void updateGPARanking(Student student) {
double gpa = student.getGPA();
gpaRanking.computeIfAbsent(gpa, k -> new ArrayList<>()).add(student);
}
private void removeFromGPARanking(Student student) {
double oldGPA = student.getGPA();
List<Student> studentsAtGPA = gpaRanking.get(oldGPA);
if (studentsAtGPA != null) {
studentsAtGPA.remove(student);
if (studentsAtGPA.isEmpty()) {
gpaRanking.remove(oldGPA);
}
}
}
// Query methods
public List<Student> getStudentsByMajor(String major) {
return majorToStudents.getOrDefault(major, Collections.emptyList())
.stream()
.map(students::get)
.collect(Collectors.toList());
}
public List<Student> getTopStudents(int limit) {
return gpaRanking.values().stream()
.flatMap(List::stream)
.limit(limit)
.collect(Collectors.toList());
}
public List<Course> getCoursesForStudent(String studentId) {
return courses.values().stream()
.filter(course -> course.getEnrolledStudents().contains(studentId))
.collect(Collectors.toList());
}
public Map<String, Long> getMajorDistribution() {
return students.values().stream()
.collect(Collectors.groupingBy(Student::getMajor, Collectors.counting()));
}
public OptionalDouble getAverageGPAByMajor(String major) {
return getStudentsByMajor(major).stream()
.mapToDouble(Student::getGPA)
.average();
}
// Display methods
public void displayAllStudents() {
System.out.println("\n=== All Students ===");
students.values().stream()
.sorted(Comparator.comparing(Student::getName))
.forEach(System.out::println);
}
public void displayStatistics() {
System.out.println("\n=== System Statistics ===");
System.out.println("Total students: " + students.size());
System.out.println("Total courses: " + courses.size());
System.out.println("\nMajor distribution:");
getMajorDistribution().entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(entry -> System.out.printf(" %s: %d students%n",
entry.getKey(), entry.getValue()));
System.out.println("\nTop 5 students by GPA:");
getTopStudents(5).forEach(student ->
System.out.printf(" %s (GPA: %.2f)%n", student.getName(), student.getGPA()));
// Average GPA by major
System.out.println("\nAverage GPA by major:");
getMajorDistribution().keySet().forEach(major -> {
OptionalDouble avgGPA = getAverageGPAByMajor(major);
System.out.printf(" %s: %.2f%n", major, avgGPA.orElse(0.0));
});
}
public static void main(String[] args) {
StudentManagementSystem sms = new StudentManagementSystem();
// Add students
sms.addStudent(new Student("S001", "Alice Johnson", 20, "Computer Science"));
sms.addStudent(new Student("S002", "Bob Smith", 21, "Mathematics"));
sms.addStudent(new Student("S003", "Charlie Brown", 19, "Computer Science"));
sms.addStudent(new Student("S004", "Diana Prince", 22, "Physics"));
sms.addStudent(new Student("S005", "Eve Adams", 20, "Mathematics"));
// Add courses
sms.addCourse(new Course("CS101", "Introduction to Programming", 3));
sms.addCourse(new Course("MATH201", "Calculus II", 4));
sms.addCourse(new Course("PHYS101", "General Physics", 4));
sms.addCourse(new Course("CS201", "Data Structures", 3));
// Enroll students
sms.enrollStudent("S001", "CS101");
sms.enrollStudent("S001", "MATH201");
sms.enrollStudent("S001", "CS201");
sms.enrollStudent("S002", "MATH201");
sms.enrollStudent("S002", "PHYS101");
sms.enrollStudent("S003", "CS101");
sms.enrollStudent("S003", "CS201");
sms.enrollStudent("S004", "PHYS101");
sms.enrollStudent("S004", "MATH201");
sms.enrollStudent("S005", "MATH201");
// Add grades
sms.addGrade("S001", "CS101", 95.0);
sms.addGrade("S001", "MATH201", 87.5);
sms.addGrade("S001", "CS201", 92.0);
sms.addGrade("S002", "MATH201", 78.5);
sms.addGrade("S002", "PHYS101", 85.0);
sms.addGrade("S003", "CS101", 88.0);
sms.addGrade("S003", "CS201", 90.5);
sms.addGrade("S004", "PHYS101", 96.0);
sms.addGrade("S004", "MATH201", 89.0);
sms.addGrade("S005", "MATH201", 82.5);
// Display results
sms.displayAllStudents();
sms.displayStatistics();
// Query examples
System.out.println("\n=== Query Examples ===");
System.out.println("Computer Science students:");
sms.getStudentsByMajor("Computer Science")
.forEach(student -> System.out.println(" " + student));
System.out.println("\nCourses for student S001:");
sms.getCoursesForStudent("S001")
.forEach(course -> System.out.println(" " + course));
}
}
Summary
The Java Collections Framework provides powerful tools for data management:
Key Takeaways
-
Choose the Right Collection:
ArrayList
for random access and frequent readsLinkedList
for frequent insertions/deletionsHashSet
for fast uniqueness checkingTreeSet
for sorted unique elementsHashMap
for key-value associationsTreeMap
for sorted key-value pairs
-
Performance Considerations:
- Set initial capacity for
HashMap
/HashSet
when size is known - Use appropriate collection for your access patterns
- Consider memory usage vs. performance trade-offs
- Set initial capacity for
-
Best Practices:
- Always override
equals()
andhashCode()
for custom objects - Use parameterized types for type safety
- Prefer immutable collections when possible
- Use
Iterator.remove()
for safe removal during iteration - Consider concurrent collections for thread-safe operations
- Always override
-
Modern Java Features:
- Stream API for complex data processing
- Collection factory methods (
List.of()
,Set.of()
,Map.of()
) - Method references and lambda expressions
Next Steps
In the next part, we’ll explore Generics (type parameters, bounds, and wildcards), which pair naturally with collections for type-safe APIs.