Java Collections Framework Deep Dive
Java Collections Framework Deep Dive
Basic Example:
import [Link].*;
public class CollectionsIntro {
public static void main(String[] args) {
// Creating ArrayList
List<String> names = new ArrayList<>();
// Adding elements
[Link]("Alice");
[Link]("Bob");
[Link]("Charlie");
// Iterating
for(String name : names) {
[Link](name);
}
// Size
[Link]("Total: " + [Link]());
}
}
2. Collection Interface Hierarchy
Understanding the hierarchy is crucial for choosing the right collection type.
3.1 ArrayList
ArrayList is a resizable array implementation. Best for frequent read operations.
import [Link].*;
public class ArrayListExample {
public static void main(String[] args) {
// Creating ArrayList
ArrayList<Integer> numbers = new ArrayList<>();
// Adding elements
[Link](10);
[Link](20);
[Link](30);
[Link](1, 15); // Insert at index 1
// Accessing elements
[Link]("Element at index 0: " + [Link](0));
// Updating
[Link](2, 25);
// Removing
[Link]([Link](10)); // Remove by value
[Link](0); // Remove by index
// Checking
[Link]("Contains 20? " + [Link](20));
// Size
[Link]("Size: " + [Link]());
// Iteration using forEach (Java 8+)
[Link](num -> [Link](num));
// Sorting
[Link](numbers);
[Link]("Sorted: " + numbers);
// Clearing
[Link]();
[Link]("Is Empty? " + [Link]());
}
}
// Output:
// Element at index 0: 10
// Contains 20? true
// Size: 2
// 20
// 30
// Sorted: [20, 30]
// Is Empty? true
Time Complexity:
import [Link].*;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> cities = new LinkedList<>();
// Adding elements
[Link]("Delhi");
[Link]("Mumbai");
[Link]("Bangalore");
// Adding at first and last
[Link]("Kolkata");
[Link]("Chennai");
[Link]("Cities: " + cities);
// Output: [Kolkata, Delhi, Mumbai, Bangalore, Chennai]
// Getting first and last
[Link]("First: " + [Link]());
[Link]("Last: " + [Link]());
// Removing first and last
[Link]();
[Link]();
[Link]("After removal: " + cities);
// Using as Queue (FIFO)
LinkedList<String> queue = new LinkedList<>();
[Link]("First"); // Add to end
[Link]("Second");
[Link]("Third");
[Link]("Poll: " + [Link]()); // Remove from front
[Link]("Queue: " + queue);
// Using as Stack (LIFO)
LinkedList<String> stack = new LinkedList<>();
[Link]("Bottom");
[Link]("Middle");
[Link]("Top");
[Link]("Pop: " + [Link]()); // Remove from top
[Link]("Stack: " + stack);
// Peek operations (don't remove)
[Link]("Peek: " + [Link]());
[Link]("Stack after peek: " + stack);
}
}
// Output:
// Cities: [Kolkata, Delhi, Mumbai, Bangalore, Chennai]
// First: Kolkata
// Last: Chennai
// After removal: [Delhi, Mumbai, Bangalore]
// Poll: First
// Queue: [Second, Third]
// Pop: Top
// Stack: [Middle, Bottom]
// Peek: Middle
// Stack after peek: [Middle, Bottom]
3.3 ArrayList vs LinkedList Comparison
Feature ArrayList LinkedList
4.1 HashSet
HashSet uses a hash table for storage. It offers constant time performance for basic operations (add, remove,
contains).
import [Link].*;
public class HashSetExample {
public static void main(String[] args) {
// Creating HashSet
HashSet<String> fruits = new HashSet<>();
// Adding elements
[Link]("Apple");
[Link]("Banana");
[Link]("Orange");
[Link]("Apple"); // Duplicate - will be ignored
[Link]("Fruits: " + fruits);
[Link]("Size: " + [Link]());
// Output: Size: 3 (duplicate not added)
// Checking existence
[Link]("Contains Banana? " + [Link]("Banana"));
// Removing
[Link]("Orange");
[Link]("After removal: " + fruits);
// Set Operations
HashSet<String> set1 = new HashSet<>([Link]("A", "B", "C"));
HashSet<String> set2 = new HashSet<>([Link]("B", "C", "D"));
// Union
HashSet<String> union = new HashSet<>(set1);
[Link](set2);
[Link]("Union: " + union); // [A, B, C, D]
// Intersection
HashSet<String> intersection = new HashSet<>(set1);
[Link](set2);
[Link]("Intersection: " + intersection); // [B, C]
// Difference
HashSet<String> difference = new HashSet<>(set1);
[Link](set2);
[Link]("Difference: " + difference); // [A]
// Iteration
[Link]("\nIterating:");
for(String fruit : fruits) {
[Link](fruit);
}
// Using Stream API (Java 8+)
[Link]()
.filter(f -> [Link]("A"))
.forEach([Link]::println);
}
}
4.2 LinkedHashSet
LinkedHashSet maintains insertion order. It's implemented as a hash table with a linked list running through it.
import [Link].*;
public class LinkedHashSetExample {
public static void main(String[] args) {
// Maintains insertion order
LinkedHashSet<Integer> numbers = new LinkedHashSet<>();
[Link](5);
[Link](2);
[Link](8);
[Link](1);
[Link](2); // Duplicate ignored
[Link]("LinkedHashSet: " + numbers);
// Output: [5, 2, 8, 1] - insertion order maintained
// Compare with HashSet (no order guarantee)
HashSet<Integer> hashSet = new HashSet<>();
[Link](5);
[Link](2);
[Link](8);
[Link](1);
[Link]("HashSet: " + hashSet);
// Output: May be [1, 2, 5, 8] or any order
// Use case: Removing duplicates while maintaining order
List<String> listWithDuplicates =
[Link]("A", "B", "A", "C", "B", "D");
LinkedHashSet<String> uniqueOrdered =
new LinkedHashSet<>(listWithDuplicates);
[Link]("Original: " + listWithDuplicates);
[Link]("Unique (ordered): " + uniqueOrdered);
// Output: Unique (ordered): [A, B, C, D]
}
}
4.3 TreeSet
TreeSet stores elements in sorted order. It's implemented using a Red-Black tree.
import [Link].*;
public class TreeSetExample {
public static void main(String[] args) {
// Elements automatically sorted
TreeSet<Integer> numbers = new TreeSet<>();
[Link](15);
[Link](5);
[Link](25);
[Link](10);
[Link](20);
[Link]("TreeSet: " + numbers);
// Output: [5, 10, 15, 20, 25] - sorted order
// NavigableSet methods
[Link]("First: " + [Link]()); // 5
[Link]("Last: " + [Link]()); // 25
[Link]("Lower than 15: " + [Link](15)); // 10
[Link]("Higher than 15: " + [Link](15)); // 20
[Link]("Floor of 16: " + [Link](16)); // 15
[Link]("Ceiling of 16: " + [Link](16)); // 20
// Subset operations
[Link]("HeadSet (<15): " + [Link](15));
// Output: [5, 10]
[Link]("TailSet (>=15): " + [Link](15));
// Output: [15, 20, 25]
[Link]("SubSet [10-20): " + [Link](10, 20));
// Output: [10, 15]
// Descending order
[Link]("Descending: " + [Link]());
// Output: [25, 20, 15, 10, 5]
// Poll operations (retrieve and remove)
[Link]("PollFirst: " + [Link]()); // 5
[Link]("PollLast: " + [Link]()); // 25
[Link]("After polling: " + numbers);
// Output: [10, 15, 20]
}
}
4.4 TreeSet with Custom Objects
import [Link].*;
class Employee implements Comparable<Employee> {
private int id;
private String name;
private double salary;
public Employee(int id, String name, double salary) {
[Link] = id;
[Link] = name;
[Link] = salary;
}
// Natural ordering by salary
@Override
public int compareTo(Employee other) {
return [Link]([Link], [Link]);
}
@Override
public String toString() {
return [Link]("Employee{id=%d, name='%s', salary=%.2f}",
id, name, salary);
}
// Getters
public int getId() { return id; }
public String getName() { return name; }
public double getSalary() { return salary; }
}
public class CustomTreeSetExample {
public static void main(String[] args) {
// Using natural ordering (Comparable)
TreeSet<Employee> empsBySalary = new TreeSet<>();
[Link](new Employee(1, "Alice", 60000));
[Link](new Employee(2, "Bob", 45000));
[Link](new Employee(3, "Charlie", 75000));
[Link]("Sorted by salary:");
[Link]([Link]::println);
// Using custom Comparator (by name)
TreeSet<Employee> empsByName = new TreeSet<>(
[Link](Employee::getName)
);
[Link](new Employee(1, "Alice", 60000));
[Link](new Employee(2, "Bob", 45000));
[Link](new Employee(3, "Charlie", 75000));
[Link]("\nSorted by name:");
[Link]([Link]::println);
// Multiple Comparators
TreeSet<Employee> empsByIdDesc = new TreeSet<>(
[Link](Employee::getId).reversed()
);
[Link](empsBySalary);
[Link]("\nSorted by ID (descending):");
[Link]([Link]::println);
}
}
// Output:
// Sorted by salary:
// Employee{id=2, name='Bob', salary=45000.00}
// Employee{id=1, name='Alice', salary=60000.00}
// Employee{id=3, name='Charlie', salary=75000.00}
//
// Sorted by name:
// Employee{id=1, name='Alice', salary=60000.00}
// Employee{id=2, name='Bob', salary=45000.00}
// Employee{id=3, name='Charlie', salary=75000.00}
5. Queue and Deque Interfaces
5.1 PriorityQueue
PriorityQueue orders elements based on their natural ordering or a custom Comparator. The head is the least
element according to the ordering.
import [Link].*;
public class PriorityQueueExample {
public static void main(String[] args) {
// Min heap (default)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
[Link](15);
[Link](5);
[Link](25);
[Link](10);
[Link]("Min Heap polling:");
while(![Link]()) {
[Link]([Link]()); // 5, 10, 15, 25
}
// Max heap (using reversed comparator)
PriorityQueue<Integer> maxHeap =
new PriorityQueue<>([Link]());
[Link](15);
[Link](5);
[Link](25);
[Link](10);
[Link]("\nMax Heap polling:");
while(![Link]()) {
[Link]([Link]()); // 25, 15, 10, 5
}
// Custom objects
PriorityQueue<Task> taskQueue = new PriorityQueue<>(
[Link](Task::getPriority)
);
[Link](new Task("Low priority", 3));
[Link](new Task("High priority", 1));
[Link](new Task("Medium priority", 2));
[Link]("\nTask execution order:");
while(![Link]()) {
[Link]([Link]());
}
}
}
class Task {
private String name;
private int priority;
public Task(String name, int priority) {
[Link] = name;
[Link] = priority;
}
public int getPriority() { return priority; }
@Override
public String toString() {
return name + " (Priority: " + priority + ")";
}
}
// Output:
// Min Heap polling:
// 5
// 10
// 15
// 25
//
// Max Heap polling:
// 25
// 15
// 10
// 5
//
// Task execution order:
// High priority (Priority: 1)
// Medium priority (Priority: 2)
// Low priority (Priority: 3)
5.2 ArrayDeque
ArrayDeque is a resizable-array implementation of Deque. It's faster than LinkedList for most operations.
import [Link].*;
public class ArrayDequeExample {
public static void main(String[] args) {
// Using as Queue (FIFO)
ArrayDeque<String> queue = new ArrayDeque<>();
[Link]("First");
[Link]("Second");
[Link]("Third");
[Link]("Queue: " + queue);
[Link]("Poll: " + [Link]());
[Link]("After poll: " + queue);
// Using as Stack (LIFO)
ArrayDeque<String> stack = new ArrayDeque<>();
[Link]("Bottom");
[Link]("Middle");
[Link]("Top");
[Link]("\nStack: " + stack);
[Link]("Pop: " + [Link]());
[Link]("After pop: " + stack);
// Deque operations (both ends)
ArrayDeque<Integer> deque = new ArrayDeque<>();
[Link](1);
[Link](2);
[Link](0);
[Link](3);
[Link]("\nDeque: " + deque); // [0, 1, 2, 3]
[Link]("First: " + [Link]());
[Link]("Last: " + [Link]());
[Link]();
[Link]();
[Link]("After removal: " + deque); // [1, 2]
// Peek operations (don't remove)
[Link]("PeekFirst: " + [Link]());
[Link]("PeekLast: " + [Link]());
// Iteration in both directions
[Link]("\nForward iteration:");
for(Integer num : deque) {
[Link](num + " ");
}
[Link]("\nReverse iteration:");
Iterator<Integer> descIter = [Link]();
while([Link]()) {
[Link]([Link]() + " ");
}
}
}
6. Map Interface
Map is an object that maps keys to values. A map cannot contain duplicate keys.
6.1 HashMap
HashMap uses hashing to store key-value pairs. It provides constant-time performance for get and put
operations.
import [Link].*;
public class HashMapExample {
public static void main(String[] args) {
// Creating HashMap
HashMap<String, Integer> scores = new HashMap<>();
// Adding entries
[Link]("Alice", 95);
[Link]("Bob", 87);
[Link]("Charlie", 92);
[Link]("Alice", 98); // Updates value for existing key
[Link]("Scores: " + scores);
// Accessing values
[Link]("Alice's score: " + [Link]("Alice"));
// Checking existence
[Link]("Contains Bob? " + [Link]("Bob"));
[Link]("Contains score 92? " + [Link](92));
// Removing
[Link]("Bob");
[Link]("After removal: " + scores);
// Size
[Link]("Size: " + [Link]());
// Iterating over keys
[Link]("\nIterating keys:");
for(String name : [Link]()) {
[Link](name + ": " + [Link](name));
}
// Iterating over values
[Link]("\nValues:");
for(Integer score : [Link]()) {
[Link](score);
}
// Iterating over entries
[Link]("\nEntries:");
for([Link]<String, Integer> entry : [Link]()) {
[Link]([Link]() + " = " + [Link]());
}
// Java 8+ forEach
[Link]("\nUsing forEach:");
[Link]((name, score) ->
[Link](name + ": " + score));
// putIfAbsent
[Link]("David", 88);
[Link]("Alice", 100); // Won't update (key exists)
// getOrDefault
[Link]("Eve's score: " +
[Link]("Eve", 0));
// compute methods
[Link]("Alice", (k, v) -> v + 2); // Increment by 2
[Link]("Charlie", (k, v) -> v + 5);
[Link]("Eve", k -> 85);
[Link]("\nAfter compute operations: " + scores);
// merge
[Link]("Alice", 5, Integer::sum); // Add 5 to existing
[Link]("After merge: " + scores);
}
}
6.2 HashMap Internals
// How HashMap works internally
1. Hash Function:
- When you put(key, value), HashMap calculates hash code
- hash = [Link]()
- Applies hash function to get bucket index
2. Bucket Structure:
- Array of buckets (default size = 16)
- Each bucket contains a linked list (or tree after Java 8)
3. Collision Handling:
- If two keys hash to same bucket → collision
- Before Java 8: Linked list
- After Java 8: Linked list → Tree (if > 8 elements)
4. Load Factor:
- Default = 0.75
- When size > capacity * loadFactor, rehashing occurs
- Capacity doubles
5. Time Complexity:
- Average: O(1) for get/put
- Worst: O(n) with many collisions (rare with good hash)
- With tree: O(log n) worst case
Example of collision:
class Student {
String name;
int rollNo;
public Student(String name, int rollNo) {
[Link] = name;
[Link] = rollNo;
}
@Override
public int hashCode() {
// Poor hash function - causes collisions
return rollNo % 10; // Only 10 possible values!
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(obj == null || getClass() != [Link]()) return false;
Student other = (Student) obj;
return rollNo == [Link] &&
[Link](name, [Link]);
}
@Override
public String toString() {
return name + "(" + rollNo + ")";
}
}
public class HashMapInternals {
public static void main(String[] args) {
HashMap<Student, String> map = new HashMap<>();
// These will likely cause collisions
[Link](new Student("Alice", 101), "A Grade");
[Link](new Student("Bob", 111), "B Grade"); // Same hash!
[Link](new Student("Charlie", 121), "A Grade"); // Same hash!
[Link](map);
// Good hash function using [Link]
// return [Link](name, rollNo);
}
}
6.3 LinkedHashMap
LinkedHashMap maintains insertion order (or access order). It's a hash table with a linked list running through it.
import [Link].*;
public class LinkedHashMapExample {
public static void main(String[] args) {
// Insertion order maintained
LinkedHashMap<String, Integer> insertionOrder =
new LinkedHashMap<>();
[Link]("Banana", 2);
[Link]("Apple", 5);
[Link]("Orange", 3);
[Link]("Mango", 4);
[Link]("Insertion order:");
[Link]((k, v) ->
[Link](k + ": " + v));
// Access order (LRU Cache)
LinkedHashMap<String, Integer> accessOrder =
new LinkedHashMap<>(16, 0.75f, true); // true = access-order
[Link]("A", 1);
[Link]("B", 2);
[Link]("C", 3);
[Link]("\nInitial: " + accessOrder);
// Access element A
[Link]("A");
[Link]("After accessing A: " + accessOrder);
// A moves to end
// LRU Cache implementation
LRUCache<String, String> cache = new LRUCache<>(3);
[Link]("1", "One");
[Link]("2", "Two");
[Link]("3", "Three");
[Link]("\nCache: " + cache);
[Link]("1"); // Access 1
[Link]("4", "Four"); // Evicts least recently used (2)
[Link]("After adding 4: " + cache);
[Link]("Contains 2? " + [Link]("2"));
}
}
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
[Link] = capacity;
}
@Override
protected boolean removeEldestEntry([Link]<K, V> eldest) {
return size() > capacity;
}
}
// Output:
// Insertion order:
// Banana: 2
// Apple: 5
// Orange: 3
// Mango: 4
//
// Initial: {A=1, B=2, C=3}
// After accessing A: {B=2, C=3, A=1}
//
// Cache: {1=One, 2=Two, 3=Three}
// After adding 4: {2=Two, 1=One, 4=Four}
// Contains 2? false
6.4 TreeMap
TreeMap stores entries in sorted order based on keys. It's implemented using a Red-Black tree.
import [Link].*;
public class TreeMapExample {
public static void main(String[] args) {
// Natural ordering (ascending)
TreeMap<Integer, String> map = new TreeMap<>();
[Link](5, "Five");
[Link](2, "Two");
[Link](8, "Eight");
[Link](1, "One");
[Link](10, "Ten");
[Link]("TreeMap: " + map);
// Output: {1=One, 2=Two, 5=Five, 8=Eight, 10=Ten}
// NavigableMap methods
[Link]("First key: " + [Link]());
[Link]("Last key: " + [Link]());
[Link]("Lower key than 5: " + [Link](5));
[Link]("Higher key than 5: " + [Link](5));
// Entry methods
[Link]("First entry: " + [Link]());
[Link]("Last entry: " + [Link]());
// Range views
[Link]("HeadMap (<5): " + [Link](5));
[Link]("TailMap (>=5): " + [Link](5));
[Link]("SubMap [2,8): " + [Link](2, 8));
// Descending map
[Link]("Descending: " + [Link]());
// Poll operations
[Link]("PollFirstEntry: " + [Link]());
[Link]("PollLastEntry: " + [Link]());
[Link]("After polling: " + map);
// Custom comparator (descending order)
TreeMap<String, Integer> reverseMap =
new TreeMap<>([Link]());
[Link]("Alice", 95);
[Link]("Bob", 87);
[Link]("Charlie", 92);
[Link]("\nReverse order: " + reverseMap);
// Use case: Frequency map
String text = "hello world";
TreeMap<Character, Integer> freqMap = new TreeMap<>();
for(char c : [Link]()) {
if(c != ' ') {
[Link](c, 1, Integer::sum);
}
}
[Link]("\nCharacter frequency: " + freqMap);
}
}
// Output:
// TreeMap: {1=One, 2=Two, 5=Five, 8=Eight, 10=Ten}
// First key: 1
// Last key: 10
// Lower key than 5: 2
// Higher key than 5: 8
// HeadMap (<5): {1=One, 2=Two}
// TailMap (>=5): {5=Five, 8=Eight, 10=Ten}
// SubMap [2,8): {2=Two, 5=Five}
7. ConcurrentHashMap and Thread-Safe Collections
7.1 ConcurrentHashMap
ConcurrentHashMap allows concurrent access without locking the entire map. It's thread-safe and more efficient
than synchronized HashMap.
import [Link].*;
import [Link].*;
public class ConcurrentHashMapExample {
public static void main(String[] args) throws InterruptedException {
// Thread-safe without synchronization
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
// Multiple threads updating
Runnable task1 = () -> {
for(int i = 0; i < 1000; i++) {
[Link]("counter", 1, Integer::sum);
}
};
Runnable task2 = () -> {
for(int i = 0; i < 1000; i++) {
[Link]("counter", 1, Integer::sum);
}
};
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
[Link]();
[Link]();
[Link]();
[Link]();
[Link]("Counter: " + [Link]("counter"));
// Output: 2000 (always correct due to thread-safety)
// Atomic operations
ConcurrentHashMap<String, Integer> scores =
new ConcurrentHashMap<>();
[Link]("Alice", 100);
// putIfAbsent - atomic
[Link]("Bob", 90);
[Link]("Alice", 95); // Won't update
// compute - atomic
[Link]("Alice", (k, v) -> v + 10);
// computeIfPresent - atomic
[Link]("Bob", (k, v) -> v + 5);
// computeIfAbsent - atomic
[Link]("Charlie", k -> 85);
[Link]("Scores: " + scores);
// Bulk operations
ConcurrentHashMap<String, Integer> data =
new ConcurrentHashMap<>();
[Link]("A", 1);
[Link]("B", 2);
[Link]("C", 3);
// forEach - parallel
[Link](1, (k, v) ->
[Link](k + "=" + v));
// reduce
int sum = [Link](1, Integer::sum);
[Link]("Sum: " + sum);
// search
String result = [Link](1, (k, v) ->
v > 2 ? k : null);
[Link]("First key with value > 2: " + result);
}
}
// ConcurrentHashMap vs Synchronized HashMap
class ComparisonExample {
public static void main(String[] args) {
// Synchronized HashMap (legacy approach)
Map<String, Integer> syncMap =
[Link](new HashMap<>());
// Locks entire map for each operation
synchronized(syncMap) {
[Link]("key", 1);
[Link]("key");
}
// ConcurrentHashMap (modern approach)
ConcurrentHashMap<String, Integer> concurrentMap =
new ConcurrentHashMap<>();
// No external synchronization needed
// Locks only segment/bucket
[Link]("key", 1);
[Link]("key");
}
}
7.2 Other Thread-Safe Collections
import [Link].*;
import [Link].*;
public class OtherConcurrentCollections {
public static void main(String[] args) throws InterruptedException {
// CopyOnWriteArrayList
// Thread-safe, but expensive for writes
// Good for read-heavy scenarios
CopyOnWriteArrayList<String> cowList =
new CopyOnWriteArrayList<>();
[Link]("A");
[Link]("B");
[Link]("C");
// Iterator doesn't throw ConcurrentModificationException
for(String s : cowList) {
[Link](s);
[Link]("D"); // Safe during iteration
}
[Link]("List: " + cowList);
// ConcurrentLinkedQueue
// Lock-free thread-safe queue
ConcurrentLinkedQueue<Integer> queue =
new ConcurrentLinkedQueue<>();
[Link](1);
[Link](2);
[Link](3);
[Link]("Poll: " + [Link]());
[Link]("Peek: " + [Link]());
// BlockingQueue - producer-consumer pattern
BlockingQueue<String> blockingQueue =
new LinkedBlockingQueue<>(10);
// Producer thread
Thread producer = new Thread(() -> {
try {
for(int i = 0; i < 5; i++) {
[Link]("Item-" + i);
[Link]("Produced: Item-" + i);
[Link](100);
}
} catch(InterruptedException e) {
[Link]().interrupt();
}
});
// Consumer thread
Thread consumer = new Thread(() -> {
try {
for(int i = 0; i < 5; i++) {
String item = [Link]();
[Link]("Consumed: " + item);
[Link](200);
}
} catch(InterruptedException e) {
[Link]().interrupt();
}
});
[Link]();
[Link]();
[Link]();
[Link]();
// ConcurrentSkipListMap
// Sorted concurrent map
ConcurrentSkipListMap<Integer, String> skipListMap =
new ConcurrentSkipListMap<>();
[Link](3, "Three");
[Link](1, "One");
[Link](2, "Two");
[Link]("\nSkipListMap: " + skipListMap);
// Output: {1=One, 2=Two, 3=Three}
}
}
8. Iterators and Iteration Techniques
import [Link].*;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>(
[Link]("A", "B", "C", "D", "E"));
// 1. Iterator
[Link]("Using Iterator:");
Iterator<String> iterator = [Link]();
while([Link]()) {
String element = [Link]();
[Link](element);
// Remove during iteration
if([Link]("C")) {
[Link](); // Safe removal
}
}
[Link]("After removal: " + list);
// 2. ListIterator (bidirectional)
[Link]("\nUsing ListIterator:");
ListIterator<String> listIter = [Link]();
while([Link]()) {
int index = [Link]();
String element = [Link]();
[Link](index + ": " + element);
// Modification during iteration
if([Link]("B")) {
[Link]("B_modified");
}
}
// Backward iteration
[Link]("\nBackward:");
while([Link]()) {
[Link]([Link]());
}
// 3. Enhanced for loop
[Link]("\nEnhanced for:");
for(String s : list) {
[Link](s);
// Cannot modify collection here!
}
// 4. forEach (Java 8+)
[Link]("\nforEach:");
[Link](s -> [Link](s));
// 5. Stream API
[Link]("\nStream:");
[Link]()
.filter(s -> [Link]() > 1)
.forEach([Link]::println);
// 6. Spliterator (for parallel processing)
[Link]("\nSpliterator:");
Spliterator<String> spliterator = [Link]();
[Link]([Link]::println);
// Fail-fast vs Fail-safe
demonstrateFailFast();
demonstrateFailSafe();
}
static void demonstrateFailFast() {
List<Integer> numbers = new ArrayList<>(
[Link](1, 2, 3, 4, 5));
try {
for(Integer num : numbers) {
[Link](num + " ");
[Link](10); // ConcurrentModificationException!
}
} catch(ConcurrentModificationException e) {
[Link]("\nFail-fast: " + [Link]().getSimpleName());
}
}
static void demonstrateFailSafe() {
// CopyOnWriteArrayList is fail-safe
CopyOnWriteArrayList<Integer> numbers =
new CopyOnWriteArrayList<>([Link](1, 2, 3, 4, 5));
[Link]("\nFail-safe iteration:");
for(Integer num : numbers) {
[Link](num + " ");
[Link](10); // No exception!
}
[Link]("\nFinal list: " + numbers);
}
}
9. Sorting and Comparators
import [Link].*;
import [Link].*;
class Student {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
[Link] = name;
[Link] = age;
[Link] = gpa;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public double getGpa() { return gpa; }
@Override
public String toString() {
return [Link]("%s (Age: %d, GPA: %.2f)",
name, age, gpa);
}
}
public class SortingExample {
public static void main(String[] args) {
// 1. Sorting primitives and strings
List<Integer> numbers = [Link](5, 2, 8, 1, 9);
[Link](numbers);
[Link]("Sorted numbers: " + numbers);
// Reverse order
[Link](numbers, [Link]());
[Link]("Reverse: " + numbers);
// 2. Sorting with Comparable
List<String> names = [Link]("Charlie", "Alice", "Bob");
[Link](names); // Uses String's compareTo
[Link]("Sorted names: " + names);
// 3. Sorting with Comparator
List<Student> students = [Link](
new Student("Alice", 20, 3.8),
new Student("Bob", 22, 3.5),
new Student("Charlie", 19, 3.9),
new Student("David", 21, 3.7)
);
// Sort by name
[Link]([Link](Student::getName));
[Link]("\nSorted by name:");
[Link]([Link]::println);
// Sort by age
[Link]([Link](Student::getAge));
[Link]("\nSorted by age:");
[Link]([Link]::println);
// Sort by GPA (descending)
[Link]([Link](Student::getGpa)
.reversed());
[Link]("\nSorted by GPA (desc):");
[Link]([Link]::println);
// 4. Multiple criteria sorting
[Link](
[Link](Student::getGpa).reversed()
.thenComparingInt(Student::getAge)
.thenComparing(Student::getName)
);
[Link]("\nMultiple criteria (GPA desc, Age, Name):");
[Link]([Link]::println);
// 5. Null-safe sorting
List<String> listWithNulls =
[Link]("Alice", null, "Bob", null, "Charlie");
[Link]([Link](
[Link]()));
[Link]("\nNulls last: " + listWithNulls);
[Link]([Link](
[Link]()));
[Link]("Nulls first: " + listWithNulls);
// 6. Custom Comparator
Comparator<Student> customComparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// GPA > 3.7 considered honors
boolean s1Honors = [Link]() > 3.7;
boolean s2Honors = [Link]() > 3.7;
if(s1Honors && !s2Honors) return -1;
if(!s1Honors && s2Honors) return 1;
return [Link]([Link](), [Link]());
}
};
[Link]([Link]());
[Link]("\nCustom sort (honors first):");
[Link]([Link]::println);
// 7. Stream sorting
List<Student> topStudents = [Link]()
.filter(s -> [Link]() > 3.6)
.sorted([Link](Student::getGpa).reversed())
.collect([Link]());
[Link]("\nTop students (GPA > 3.6):");
[Link]([Link]::println);
// 8. Array sorting
int[] arr = {5, 2, 8, 1, 9};
[Link](arr);
[Link]("\nSorted array: " + [Link](arr));
// Parallel sort (for large arrays)
[Link](arr);
}
}
10. Performance Comparison and Best Practices
10.1 Time Complexity Comparison
Operation ArrayList LinkedList HashSet TreeSet
Thread-safe No No No Yes
10.3 Best Practices
Choose the right collection:
Use ArrayList for random access, LinkedList for frequent insertions/deletions, HashSet for uniqueness, TreeSet
for sorted uniqueness
Prefer interfaces:
List list = new ArrayList<>() not ArrayList list
Solution: Don't modify collection while iterating. Use [Link]() or concurrent collections.
Solution: Objects used as HashMap keys must override both methods consistently.
Solution: These don't guarantee order. Use LinkedHashSet/LinkedHashMap for insertion order.
Solution: HashMap allows one null key, HashSet one null element. TreeMap/TreeSet don't allow nulls.
Summary and Quick Reference
Collection Framework Summary:
1. WHEN TO USE WHAT:
- ArrayList: Fast random access, infrequent insertions
- LinkedList: Frequent insertions/deletions, queue operations
- HashSet: Fast lookup, no duplicates, no order needed
- LinkedHashSet: No duplicates, maintain insertion order
- TreeSet: No duplicates, sorted order
- HashMap: Fast key-value lookup, no order needed
- LinkedHashMap: Key-value pairs, maintain insertion order
- TreeMap: Key-value pairs, sorted by keys
- PriorityQueue: Process elements by priority
- ArrayDeque: Stack or queue operations
2. THREAD SAFETY:
- ConcurrentHashMap: Thread-safe map
- CopyOnWriteArrayList: Thread-safe list (read-heavy)
- [Link](): Synchronized wrappers
3. KEY CONCEPTS:
- Always override hashCode() and equals() for HashMap/HashSet keys
- Use interfaces (List, Set, Map) not implementations
- Prefer enhanced for loop or forEach for iteration
- Use Streams for complex data transformations
- Specify initial capacity when size is known
- Use Comparator for flexible sorting
4. PERFORMANCE TIPS:
- HashMap/HashSet: O(1) for basic operations
- TreeMap/TreeSet: O(log n) for operations
- ArrayList: O(1) get, O(n) insert/remove
- LinkedList: O(n) get, O(1) insert/remove (with node ref)
5. MODERN JAVA (8+):
- forEach(): [Link](item -> ...)
- Stream API: [Link]().filter().map().collect()
- Collectors: [Link](), groupingBy(), etc.
- Method references: [Link]([Link]::println)
This guide covered the Java Collections Framework from basics to advanced topics. Practice these examples,
understand the trade-offs between different collections, and choose the right tool for each use case. Keep this
PDF as a quick reference for your daily development work.