Java Collections Framework - Complete Tutorial
Master Java Collections: Learn List, Set, Map, Queue interfaces with ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, PriorityQueue implementations, iterators, comparators and best practices.
List Interface
ArrayList, LinkedList
Set Interface
HashSet, TreeSet
Map Interface
HashMap, TreeMap
Queue Interface
PriorityQueue
1. Introduction to Java Collections Framework
The Java Collections Framework (JCF) is a unified architecture for representing and manipulating collections. It provides ready-to-use data structures and algorithms to handle groups of objects efficiently.
Why Collections Framework?
- Reduces Programming Effort: Ready-to-use data structures
- Increases Performance: High-performance implementations
- Provides Interoperability: Standard interface for collections
- Reduces Learning Effort: Consistent API across types
- Software Reuse: Can be easily extended
- Type Safety: Generics provide compile-time type checking
Key Interfaces
- Collection: Root interface
- List: Ordered, indexed, allows duplicates
- Set: No duplicates, unordered (except TreeSet)
- Map: Key-value pairs, no duplicate keys
- Queue: FIFO or priority-based ordering
- Deque: Double-ended queue
- SortedSet & SortedMap: Sorted collections
Collections Framework Hierarchy
JCF follows a well-defined hierarchy: Iterable → Collection → (List, Set, Queue) & Map → Concrete Implementations. Understanding this hierarchy is key to mastering collections.
Java Collections Framework Hierarchy
Iterable (Interface)
↓
Collection (Interface)
├── List (Interface)
│ ├── ArrayList (Class)
│ ├── LinkedList (Class)
│ └── Vector (Class)
│ └── Stack (Class)
│
├── Set (Interface)
│ ├── HashSet (Class)
│ │ └── LinkedHashSet (Class)
│ └── SortedSet (Interface)
│ └── TreeSet (Class)
│
└── Queue (Interface)
├── PriorityQueue (Class)
└── Deque (Interface)
└── ArrayDeque (Class)
Map (Interface) - NOT part of Collection hierarchy
├── HashMap (Class)
│ └── LinkedHashMap (Class)
├── Hashtable (Class)
└── SortedMap (Interface)
└── TreeMap (Class)
List
Ordered CollectionIndex-based access, allows duplicates
Set
Unique ElementsNo duplicates, unordered (except TreeSet)
Map
Key-Value PairsUnique keys, values can be duplicated
Queue
FIFO/PriorityOrder processing, priority-based
2. List Interface and Implementations
The List interface represents an ordered collection (sequence). It allows positional access, duplicate elements, and null values. Main implementations: ArrayList, LinkedList, and Vector.
import java.util.*;
public class ListExamples {
public static void main(String[] args) {
System.out.println("=== ArrayList Examples ===");
// ArrayList creation
List arrayList = new ArrayList<>();
// Adding elements
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
arrayList.add("Date");
arrayList.add("Elderberry");
System.out.println("ArrayList: " + arrayList);
System.out.println("Size: " + arrayList.size());
System.out.println("Is empty: " + arrayList.isEmpty());
// Accessing elements
System.out.println("\n=== Access Operations ===");
System.out.println("First element: " + arrayList.get(0));
System.out.println("Last element: " + arrayList.get(arrayList.size() - 1));
System.out.println("Index of 'Cherry': " + arrayList.indexOf("Cherry"));
System.out.println("Contains 'Banana': " + arrayList.contains("Banana"));
// Modifying elements
System.out.println("\n=== Modification Operations ===");
arrayList.set(2, "Coconut"); // Replace at index 2
System.out.println("After set(2, 'Coconut'): " + arrayList);
arrayList.add(3, "Dragonfruit"); // Insert at index 3
System.out.println("After add(3, 'Dragonfruit'): " + arrayList);
arrayList.remove("Banana"); // Remove by object
System.out.println("After remove('Banana'): " + arrayList);
arrayList.remove(0); // Remove by index
System.out.println("After remove(0): " + arrayList);
// Sublist
System.out.println("\n=== Sublist Operations ===");
List subList = arrayList.subList(1, 3);
System.out.println("Sublist(1, 3): " + subList);
// Bulk operations
System.out.println("\n=== Bulk Operations ===");
List fruitsToAdd = Arrays.asList("Fig", "Grape", "Honeydew");
arrayList.addAll(fruitsToAdd);
System.out.println("After addAll: " + arrayList);
arrayList.removeAll(Arrays.asList("Coconut", "Dragonfruit"));
System.out.println("After removeAll: " + arrayList);
arrayList.retainAll(Arrays.asList("Date", "Elderberry", "Fig"));
System.out.println("After retainAll: " + arrayList);
// Iteration
System.out.println("\n=== Iteration Methods ===");
System.out.print("Using for-each: ");
for (String fruit : arrayList) {
System.out.print(fruit + " ");
}
System.out.print("\nUsing iterator: ");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.print("\nUsing listIterator (backwards): ");
ListIterator listIterator = arrayList.listIterator(arrayList.size());
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + " ");
}
System.out.print("\nUsing forEach (Java 8+): ");
arrayList.forEach(fruit -> System.out.print(fruit + " "));
System.out.println("\n\n=== LinkedList Examples ===");
List linkedList = new LinkedList<>();
linkedList.add("First");
linkedList.add("Second");
linkedList.add("Third");
System.out.println("LinkedList: " + linkedList);
// LinkedList specific operations (when cast to LinkedList)
LinkedList ll = (LinkedList) linkedList;
ll.addFirst("Zero");
ll.addLast("Fourth");
System.out.println("After addFirst and addLast: " + ll);
System.out.println("First element: " + ll.getFirst());
System.out.println("Last element: " + ll.getLast());
System.out.println("Removed first: " + ll.removeFirst());
System.out.println("Removed last: " + ll.removeLast());
System.out.println("Final LinkedList: " + ll);
// Sorting
System.out.println("\n=== Sorting Lists ===");
List numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3));
System.out.println("Original: " + numbers);
Collections.sort(numbers);
System.out.println("Sorted ascending: " + numbers);
Collections.sort(numbers, Collections.reverseOrder());
System.out.println("Sorted descending: " + numbers);
// Custom sorting with Comparator
List names = new ArrayList<>(Arrays.asList("John", "Alice", "Bob", "Charlie"));
System.out.println("\nNames original: " + names);
Collections.sort(names);
System.out.println("Names sorted naturally: " + names);
Collections.sort(names, (a, b) -> a.length() - b.length());
System.out.println("Names sorted by length: " + names);
// List to array and vice versa
System.out.println("\n=== Array Conversion ===");
String[] array = arrayList.toArray(new String[0]);
System.out.println("Array from list: " + Arrays.toString(array));
List listFromArray = Arrays.asList(array);
System.out.println("List from array: " + listFromArray);
// Clear list
arrayList.clear();
System.out.println("\nAfter clear(), size: " + arrayList.size());
}
}
| Implementation | When to Use | Key Features | Time Complexity |
|---|---|---|---|
ArrayList |
Frequent read operations, random access | Resizable array, fast random access | Get/Set: O(1) Add/Remove at end: O(1) Add/Remove at index: O(n) |
LinkedList |
Frequent add/remove operations | Doubly-linked list, queue operations | Get/Set: O(n) Add/Remove at ends: O(1) Add/Remove at index: O(n) |
Vector |
Thread-safe operations needed | Synchronized, legacy class | Similar to ArrayList but slower due to synchronization |
ArrayList Methods Reference
add(E e) |
Appends element |
add(int index, E e) |
Inserts at position |
get(int index) |
Returns element at index |
set(int index, E e) |
Replaces element at index |
remove(int index) |
Removes element at index |
remove(Object o) |
Removes first occurrence |
indexOf(Object o) |
Returns first index |
lastIndexOf(Object o) |
Returns last index |
subList(int from, int to) |
Returns sublist view |
LinkedList Methods Reference
addFirst(E e) |
Inserts at beginning |
addLast(E e) |
Appends to end |
getFirst() |
Returns first element |
getLast() |
Returns last element |
removeFirst() |
Removes first element |
removeLast() |
Removes last element |
offer(E e) |
Adds as last (queue) |
poll() |
Removes first (queue) |
peek() |
Retrieves first (queue) |
- Use ArrayList when: More get/set operations, random access needed, less insertions/deletions
- Use LinkedList when: More insertions/deletions, frequently adding/removing at ends, implementing stack/queue
- Memory: ArrayList uses less memory per element
- Performance: ArrayList better for iteration, LinkedList better for manipulation
- Rule of thumb: Default to ArrayList unless you specifically need LinkedList features
3. Set Interface and Implementations
The Set interface represents a collection that cannot contain duplicate elements. It models the mathematical set abstraction. Main implementations: HashSet, LinkedHashSet, and TreeSet.
import java.util.*;
public class SetExamples {
public static void main(String[] args) {
System.out.println("=== HashSet Examples ===");
// HashSet - unordered, uses HashMap internally
Set hashSet = new HashSet<>();
// Adding elements (duplicates ignored)
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
hashSet.add("Apple"); // Duplicate - ignored
hashSet.add("Date");
hashSet.add(null); // Allows null
System.out.println("HashSet: " + hashSet);
System.out.println("Size: " + hashSet.size());
System.out.println("Is empty: " + hashSet.isEmpty());
System.out.println("Contains 'Banana': " + hashSet.contains("Banana"));
// Removing elements
hashSet.remove("Date");
System.out.println("After remove('Date'): " + hashSet);
// Bulk operations
Set otherFruits = new HashSet<>(Arrays.asList("Fig", "Grape", "Apple"));
hashSet.addAll(otherFruits);
System.out.println("After addAll: " + hashSet);
hashSet.retainAll(Arrays.asList("Apple", "Banana", "Fig"));
System.out.println("After retainAll: " + hashSet);
hashSet.removeAll(Arrays.asList("Fig"));
System.out.println("After removeAll: " + hashSet);
// Iteration (order not guaranteed)
System.out.print("\nIteration (unordered): ");
for (String fruit : hashSet) {
System.out.print(fruit + " ");
}
// Clear set
hashSet.clear();
System.out.println("\nAfter clear, size: " + hashSet.size());
System.out.println("\n=== LinkedHashSet Examples ===");
// LinkedHashSet - maintains insertion order
Set linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Zebra");
linkedHashSet.add("Apple");
linkedHashSet.add("Monkey");
linkedHashSet.add("Banana");
System.out.println("LinkedHashSet (insertion order): " + linkedHashSet);
System.out.print("Iteration (insertion order): ");
linkedHashSet.forEach(item -> System.out.print(item + " "));
System.out.println("\n\n=== TreeSet Examples ===");
// TreeSet - sorted order (natural or comparator)
Set treeSet = new TreeSet<>();
treeSet.add("Orange");
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Cherry");
treeSet.add("Apple"); // Duplicate ignored
System.out.println("TreeSet (sorted naturally): " + treeSet);
// TreeSet with custom comparator
Set reverseTreeSet = new TreeSet<>(Collections.reverseOrder());
reverseTreeSet.addAll(Arrays.asList("Orange", "Apple", "Banana", "Cherry"));
System.out.println("TreeSet (reverse order): " + reverseTreeSet);
// TreeSet operations
System.out.println("\n=== TreeSet Specific Operations ===");
TreeSet numbers = new TreeSet<>(Arrays.asList(10, 5, 15, 3, 7, 12, 18));
System.out.println("Numbers TreeSet: " + numbers);
System.out.println("First (lowest): " + numbers.first());
System.out.println("Last (highest): " + numbers.last());
System.out.println("Lower than 7: " + numbers.lower(7)); // greatest < 7
System.out.println("Higher than 7: " + numbers.higher(7)); // least > 7
System.out.println("Floor of 8: " + numbers.floor(8)); // greatest ≤ 8
System.out.println("Ceiling of 8: " + numbers.ceiling(8)); // least ≥ 8
// Subset operations
System.out.println("\n=== Subset Operations ===");
System.out.println("HeadSet (<10): " + numbers.headSet(10)); // elements < 10
System.out.println("TailSet (≥10): " + numbers.tailSet(10)); // elements ≥ 10
System.out.println("SubSet (5 to 15): " + numbers.subSet(5, 15)); // 5 ≤ elements < 15
System.out.println("SubSet inclusive (5 to 15): " + numbers.subSet(5, true, 15, true)); // 5 ≤ elements ≤ 15
// Poll operations (remove and return)
System.out.println("\n=== Poll Operations ===");
System.out.println("Poll first (remove lowest): " + numbers.pollFirst());
System.out.println("Poll last (remove highest): " + numbers.pollLast());
System.out.println("After polling: " + numbers);
// Set operations - Union, Intersection, Difference
System.out.println("\n=== Set Operations (Mathematical) ===");
Set setA = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set setB = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
System.out.println("Set A: " + setA);
System.out.println("Set B: " + setB);
// Union (A ∪ B)
Set union = new HashSet<>(setA);
union.addAll(setB);
System.out.println("Union (A ∪ B): " + union);
// Intersection (A ∩ B)
Set intersection = new HashSet<>(setA);
intersection.retainAll(setB);
System.out.println("Intersection (A ∩ B): " + intersection);
// Difference (A - B)
Set difference = new HashSet<>(setA);
difference.removeAll(setB);
System.out.println("Difference (A - B): " + difference);
// Symmetric Difference (A Δ B) = (A ∪ B) - (A ∩ B)
Set symmetricDiff = new HashSet<>(union);
symmetricDiff.removeAll(intersection);
System.out.println("Symmetric Difference (A Δ B): " + symmetricDiff);
// Check subset
System.out.println("Is {4,5} subset of A? " + setA.containsAll(Arrays.asList(4, 5)));
System.out.println("Is A subset of union? " + union.containsAll(setA));
// Working with custom objects in HashSet
System.out.println("\n=== Custom Objects in HashSet ===");
Set students = new HashSet<>();
students.add(new Student(101, "Alice"));
students.add(new Student(102, "Bob"));
students.add(new Student(101, "Alice")); // Duplicate based on equals/hashCode
System.out.println("Students set size: " + students.size());
students.forEach(System.out::println);
}
static class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// Must override equals and hashCode for HashSet to work correctly
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "'}";
}
}
}
| Implementation | Ordering | Null Elements | Performance | When to Use |
|---|---|---|---|---|
HashSet |
No ordering | Allows one null | O(1) for add, remove, contains | General-purpose set, don't care about order |
LinkedHashSet |
Insertion order | Allows one null | Slightly slower than HashSet | Need insertion-order iteration |
TreeSet |
Sorted (natural or comparator) | No nulls if natural ordering | O(log n) for operations | Need sorted set, range queries |
HashSet Methods Reference
add(E e) |
Adds element if not present |
remove(Object o) |
Removes element |
contains(Object o) |
Checks for element |
size() |
Returns number of elements |
isEmpty() |
Checks if set is empty |
clear() |
Removes all elements |
iterator() |
Returns iterator |
TreeSet Methods Reference
first() |
Returns first (lowest) element |
last() |
Returns last (highest) element |
pollFirst() |
Removes and returns first |
pollLast() |
Removes and returns last |
lower(E e) |
Greatest element < e |
higher(E e) |
Least element > e |
floor(E e) |
Greatest element ≤ e |
ceiling(E e) |
Least element ≥ e |
- equals() and hashCode(): Must be properly overridden for custom objects in HashSet/LinkedHashSet
- Comparable/Comparator: Required for TreeSet elements
- HashSet performance: Depends on good hashCode() implementation
- Null values: HashSet/LinkedHashSet allow one null, TreeSet may not allow nulls
- Thread safety: None are thread-safe (use Collections.synchronizedSet())
- Backing: HashSet uses HashMap, LinkedHashSet uses LinkedHashMap, TreeSet uses TreeMap
4. Map Interface and Implementations
The Map interface represents a key-value pair collection. Keys are unique, values can be duplicated. Main implementations: HashMap, LinkedHashMap, TreeMap, and Hashtable.
import java.util.*;
public class MapExamples {
public static void main(String[] args) {
System.out.println("=== HashMap Examples ===");
// HashMap - unordered, allows one null key and multiple null values
Map hashMap = new HashMap<>();
// Adding key-value pairs
hashMap.put("Apple", 10);
hashMap.put("Banana", 20);
hashMap.put("Cherry", 30);
hashMap.put("Apple", 15); // Updates existing key
hashMap.put(null, 5); // Allows null key
hashMap.put("Date", null); // Allows null value
System.out.println("HashMap: " + hashMap);
System.out.println("Size: " + hashMap.size());
System.out.println("Is empty: " + hashMap.isEmpty());
// Access operations
System.out.println("\n=== Access Operations ===");
System.out.println("Value for 'Apple': " + hashMap.get("Apple"));
System.out.println("Value for 'Mango' (default): " + hashMap.getOrDefault("Mango", 0));
System.out.println("Contains key 'Banana': " + hashMap.containsKey("Banana"));
System.out.println("Contains value 30: " + hashMap.containsValue(30));
// Key set, values, and entry set
System.out.println("\n=== Views ===");
System.out.println("Keys: " + hashMap.keySet());
System.out.println("Values: " + hashMap.values());
System.out.println("Entries: " + hashMap.entrySet());
// Modification operations
System.out.println("\n=== Modification Operations ===");
hashMap.remove("Banana");
System.out.println("After remove('Banana'): " + hashMap);
hashMap.replace("Cherry", 35);
System.out.println("After replace('Cherry', 35): " + hashMap);
hashMap.replace("Cherry", 35, 40); // Replace if old value matches
System.out.println("After conditional replace: " + hashMap);
hashMap.putIfAbsent("Mango", 25); // Put if key absent
System.out.println("After putIfAbsent('Mango', 25): " + hashMap);
// Compute operations (Java 8+)
System.out.println("\n=== Compute Operations ===");
hashMap.compute("Apple", (k, v) -> v + 10); // Compute new value
System.out.println("After compute on 'Apple': " + hashMap);
hashMap.computeIfAbsent("Orange", k -> 50); // Compute if absent
System.out.println("After computeIfAbsent('Orange'): " + hashMap);
hashMap.computeIfPresent("Cherry", (k, v) -> v * 2); // Compute if present
System.out.println("After computeIfPresent('Cherry'): " + hashMap);
hashMap.merge("Apple", 5, (oldVal, newVal) -> oldVal + newVal); // Merge
System.out.println("After merge on 'Apple': " + hashMap);
// Iteration
System.out.println("\n=== Iteration Methods ===");
System.out.println("Using entrySet:");
for (Map.Entry entry : hashMap.entrySet()) {
System.out.println(" Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
System.out.println("\nUsing forEach (Java 8+):");
hashMap.forEach((key, value) -> System.out.println(" " + key + " -> " + value));
System.out.println("\nUsing keySet:");
for (String key : hashMap.keySet()) {
System.out.println(" Key: " + key + ", Value: " + hashMap.get(key));
}
System.out.println("\n=== LinkedHashMap Examples ===");
// LinkedHashMap - maintains insertion order
Map linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("Zebra", 100);
linkedHashMap.put("Apple", 200);
linkedHashMap.put("Monkey", 300);
System.out.println("LinkedHashMap (insertion order): " + linkedHashMap);
// Access-order LinkedHashMap (for LRU cache)
Map accessOrderMap = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 3; // Remove eldest when size > 3
}
};
accessOrderMap.put("A", 1);
accessOrderMap.put("B", 2);
accessOrderMap.put("C", 3);
accessOrderMap.get("A"); // Access moves to end
accessOrderMap.put("D", 4); // Should remove eldest (B)
System.out.println("LRU LinkedHashMap: " + accessOrderMap);
System.out.println("\n=== TreeMap Examples ===");
// TreeMap - sorted by keys
Map treeMap = new TreeMap<>();
treeMap.put("Orange", 50);
treeMap.put("Apple", 30);
treeMap.put("Banana", 40);
treeMap.put("Cherry", 60);
System.out.println("TreeMap (sorted by keys): " + treeMap);
// TreeMap with custom comparator
Map reverseTreeMap = new TreeMap<>(Collections.reverseOrder());
reverseTreeMap.putAll(treeMap);
System.out.println("TreeMap (reverse order): " + reverseTreeMap);
// TreeMap specific operations
System.out.println("\n=== TreeMap Specific Operations ===");
TreeMap numberMap = new TreeMap<>();
numberMap.put(10, "Ten");
numberMap.put(5, "Five");
numberMap.put(15, "Fifteen");
numberMap.put(3, "Three");
numberMap.put(7, "Seven");
System.out.println("Number TreeMap: " + numberMap);
System.out.println("First key: " + numberMap.firstKey());
System.out.println("Last key: " + numberMap.lastKey());
System.out.println("First entry: " + numberMap.firstEntry());
System.out.println("Last entry: " + numberMap.lastEntry());
// Navigable map operations
System.out.println("\n=== Navigable Map Operations ===");
System.out.println("Lower key than 8: " + numberMap.lowerKey(8)); // greatest key < 8
System.out.println("Higher key than 8: " + numberMap.higherKey(8)); // least key > 8
System.out.println("Floor key of 8: " + numberMap.floorKey(8)); // greatest key ≤ 8
System.out.println("Ceiling key of 8: " + numberMap.ceilingKey(8)); // least key ≥ 8
// Submap operations
System.out.println("\n=== Submap Operations ===");
System.out.println("HeadMap (<10): " + numberMap.headMap(10)); // keys < 10
System.out.println("TailMap (≥10): " + numberMap.tailMap(10)); // keys ≥ 10
System.out.println("SubMap (5 to 15): " + numberMap.subMap(5, 15)); // 5 ≤ keys < 15
// Poll operations
System.out.println("\n=== Poll Operations ===");
System.out.println("Poll first entry: " + numberMap.pollFirstEntry());
System.out.println("Poll last entry: " + numberMap.pollLastEntry());
System.out.println("After polling: " + numberMap);
// Working with custom objects as keys
System.out.println("\n=== Custom Objects as Map Keys ===");
Map employeeMap = new HashMap<>();
employeeMap.put(new Employee(101, "Alice"), "Developer");
employeeMap.put(new Employee(102, "Bob"), "Manager");
employeeMap.put(new Employee(101, "Alice"), "Senior Developer"); // Updates
System.out.println("Employee Map size: " + employeeMap.size());
employeeMap.forEach((k, v) -> System.out.println(k + " -> " + v));
}
static class Employee {
private int id;
private String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
// Must override equals and hashCode for HashMap key
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Employee{id=" + id + ", name='" + name + "'}";
}
}
}
| Implementation | Ordering | Null Keys/Values | Performance | Thread Safe |
|---|---|---|---|---|
HashMap |
No ordering | 1 null key, multiple null values | O(1) average | No |
LinkedHashMap |
Insertion/access order | 1 null key, multiple null values | Slightly slower than HashMap | No |
TreeMap |
Sorted by keys | No null keys if natural ordering | O(log n) | No |
Hashtable |
No ordering | No null keys or values | O(1) average | Yes (legacy) |
HashMap Methods
put(K, V) |
Adds/replaces key-value |
get(K) |
Returns value for key |
remove(K) |
Removes key-value pair |
containsKey(K) |
Checks if key exists |
containsValue(V) |
Checks if value exists |
TreeMap Methods
firstKey() |
Returns first key |
lastKey() |
Returns last key |
lowerKey(K) |
Key strictly less than |
higherKey(K) |
Key strictly greater than |
pollFirstEntry() |
Removes first entry |
LinkedHashMap
| Constructor options: | |
new LinkedHashMap() |
Insertion order |
new LinkedHashMap(16, 0.75f, true) |
Access order (LRU) |
| Use for: LRU cache, predictable iteration | |
- Use HashMap when: No ordering needed, fastest operations, most common use case
- Use TreeMap when: Need sorted keys, range queries, floor/ceiling operations
- Use LinkedHashMap when: Need insertion-order or access-order iteration, LRU cache implementation
- Performance: HashMap O(1), TreeMap O(log n), LinkedHashMap slightly slower than HashMap
- Memory: HashMap uses less memory than LinkedHashMap
- Thread safety: None are thread-safe (use ConcurrentHashMap or Collections.synchronizedMap())
5. Queue Interface and Implementations
The Queue interface represents a collection designed for holding elements prior to processing. Follows FIFO (First-In-First-Out) or priority ordering. Main implementations: PriorityQueue, ArrayDeque.
import java.util.*;
public class QueueExamples {
public static void main(String[] args) {
System.out.println("=== Queue Interface Basics ===");
// Queue operations follow FIFO (First-In-First-Out)
Queue queue = new LinkedList<>();
// Adding elements
queue.add("First"); // Throws exception if full
queue.offer("Second"); // Returns false if full
queue.offer("Third");
queue.offer("Fourth");
System.out.println("Queue: " + queue);
System.out.println("Size: " + queue.size());
System.out.println("Is empty: " + queue.isEmpty());
// Examining elements
System.out.println("\n=== Examining Elements ===");
System.out.println("Peek (head): " + queue.peek()); // Returns null if empty
System.out.println("Element (head): " + queue.element()); // Throws exception if empty
// Removing elements
System.out.println("\n=== Removing Elements ===");
System.out.println("Poll (remove head): " + queue.poll()); // Returns null if empty
System.out.println("Remove (remove head): " + queue.remove()); // Throws exception if empty
System.out.println("Queue after removals: " + queue);
// Clear queue
queue.clear();
System.out.println("After clear, is empty: " + queue.isEmpty());
System.out.println("\n=== PriorityQueue Examples ===");
// PriorityQueue - elements ordered by natural ordering or comparator
Queue priorityQueue = new PriorityQueue<>();
priorityQueue.offer(30);
priorityQueue.offer(10);
priorityQueue.offer(20);
priorityQueue.offer(5);
priorityQueue.offer(25);
System.out.println("PriorityQueue (min-heap by default): " + priorityQueue);
System.out.println("Note: toString() doesn't show priority order!");
System.out.println("\nProcessing in priority order:");
while (!priorityQueue.isEmpty()) {
System.out.print(priorityQueue.poll() + " "); // Removes in sorted order
}
// PriorityQueue with custom comparator (max-heap)
System.out.println("\n\n=== Max-Heap PriorityQueue ===");
Queue maxHeap = new PriorityQueue<>(Collections.reverseOrder());
maxHeap.addAll(Arrays.asList(30, 10, 20, 5, 25));
System.out.println("Processing max-heap (largest first):");
while (!maxHeap.isEmpty()) {
System.out.print(maxHeap.poll() + " ");
}
// PriorityQueue with custom objects
System.out.println("\n\n=== PriorityQueue with Custom Objects ===");
Queue studentQueue = new PriorityQueue<>(Comparator.comparing(Student::getGrade));
studentQueue.offer(new Student("Alice", 85));
studentQueue.offer(new Student("Bob", 92));
studentQueue.offer(new Student("Charlie", 78));
studentQueue.offer(new Student("Diana", 95));
System.out.println("Students by grade (lowest first):");
while (!studentQueue.isEmpty()) {
System.out.println(studentQueue.poll());
}
System.out.println("\n=== ArrayDeque Examples ===");
// ArrayDeque - resizable array implementation of Deque
Deque deque = new ArrayDeque<>();
// Add operations
deque.addFirst("Front"); // Add to front
deque.addLast("Middle"); // Add to end
deque.offerFirst("Very Front");
deque.offerLast("End");
System.out.println("Deque: " + deque);
// Examine operations
System.out.println("First element: " + deque.getFirst());
System.out.println("Last element: " + deque.getLast());
System.out.println("Peek first: " + deque.peekFirst());
System.out.println("Peek last: " + deque.peekLast());
// Remove operations
System.out.println("\n=== Remove Operations ===");
System.out.println("Remove first: " + deque.removeFirst());
System.out.println("Remove last: " + deque.removeLast());
System.out.println("Poll first: " + deque.pollFirst());
System.out.println("Poll last: " + deque.pollLast());
System.out.println("Deque after removals: " + deque);
// Stack operations using Deque (recommended over Stack class)
System.out.println("\n=== Stack Operations (LIFO) ===");
Deque stack = new ArrayDeque<>();
stack.push(10); // Push to top
stack.push(20);
stack.push(30);
System.out.println("Stack: " + stack);
System.out.println("Peek (top): " + stack.peek());
System.out.println("Pop (remove top): " + stack.pop());
System.out.println("After pop: " + stack);
// Queue operations using Deque
System.out.println("\n=== Queue Operations (FIFO) using Deque ===");
Deque fifoQueue = new ArrayDeque<>();
fifoQueue.offerLast("First");
fifoQueue.offerLast("Second");
fifoQueue.offerLast("Third");
System.out.println("Queue: " + fifoQueue);
System.out.println("Process in FIFO order:");
while (!fifoQueue.isEmpty()) {
System.out.println(fifoQueue.pollFirst());
}
// Deque as sliding window
System.out.println("\n=== Deque for Sliding Window Maximum ===");
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
System.out.println("Sliding window maximum for window size " + k + ":");
System.out.println(Arrays.toString(maxSlidingWindow(nums, k)));
}
static class Student {
private String name;
private int grade;
public Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
public String getName() { return name; }
public int getGrade() { return grade; }
@Override
public String toString() {
return name + " (" + grade + ")";
}
}
// Sliding window maximum using Deque
public static int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k <= 0) return new int[0];
int n = nums.length;
int[] result = new int[n - k + 1];
Deque deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// Remove indices out of window
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
// Remove smaller elements from back
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
// Add to result when window is formed
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
}
| Implementation | Ordering | Null Elements | Performance | Use Cases |
|---|---|---|---|---|
PriorityQueue |
Priority (natural or comparator) | Not allowed | O(log n) for insert/remove | Task scheduling, Dijkstra's algorithm |
ArrayDeque |
FIFO/LIFO (both ends) | Not allowed | O(1) for add/remove at ends | Stack, Queue, Deque implementations |
LinkedList |
FIFO (as Queue) | Allowed | O(1) for add/remove at ends | Simple queue, when nulls needed |
PriorityQueue Methods
offer(E e) |
Inserts element |
poll() |
Retrieves and removes head |
peek() |
Retrieves head without removing |
comparator() |
Returns comparator used |
| Note: Not thread-safe, use PriorityBlockingQueue for thread safety | |
Deque Methods
addFirst(E)/addLast(E) |
Adds to front/back |
removeFirst()/removeLast() |
Removes from front/back |
getFirst()/getLast() |
Returns front/back element |
push(E)/pop() |
Stack operations |
| Use ArrayDeque for stack/queue, not Stack class | |
6. Iterators, Comparators and Utilities
Java Collections Framework provides powerful utilities for iterating over collections and defining custom ordering through iterators, comparators, and the Collections utility class.
import java.util.*;
public class IteratorExamples {
public static void main(String[] args) {
System.out.println("=== Iterator Examples ===");
List fruits = new ArrayList<>(Arrays.asList(
"Apple", "Banana", "Cherry", "Date", "Elderberry"
));
// Basic Iterator
System.out.println("Using Iterator:");
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.print(fruit + " ");
// Remove during iteration
if (fruit.startsWith("B")) {
iterator.remove(); // Safe removal
}
}
System.out.println("\nAfter removing 'B' fruits: " + fruits);
// ListIterator (bidirectional)
System.out.println("\n=== ListIterator (Bidirectional) ===");
ListIterator listIterator = fruits.listIterator();
// Forward iteration
System.out.print("Forward: ");
while (listIterator.hasNext()) {
System.out.print(listIterator.next() + " ");
}
// Backward iteration
System.out.print("\nBackward: ");
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() + " ");
}
// ListIterator modification
listIterator = fruits.listIterator();
listIterator.next(); // Move to first
listIterator.add("Apricot"); // Add at current position
listIterator.next();
listIterator.set("Blueberry"); // Replace current
System.out.println("\nAfter ListIterator modifications: " + fruits);
// For-each (enhanced for loop) - internally uses iterator
System.out.print("\nFor-each loop: ");
for (String fruit : fruits) {
System.out.print(fruit + " ");
}
// Java 8+ forEach with lambda
System.out.print("\nforEach with lambda: ");
fruits.forEach(fruit -> System.out.print(fruit + " "));
System.out.print("\nforEach with method reference: ");
fruits.forEach(System.out::print);
// Iterator for Set
System.out.println("\n\n=== Iterator for Set ===");
Set numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40, 50));
Iterator setIterator = numbers.iterator();
System.out.print("Set iteration: ");
while (setIterator.hasNext()) {
System.out.print(setIterator.next() + " ");
}
// Iterator for Map (entrySet, keySet, values)
System.out.println("\n\n=== Iterator for Map ===");
Map map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println("Using entrySet iterator:");
Iterator> mapIterator = map.entrySet().iterator();
while (mapIterator.hasNext()) {
Map.Entry entry = mapIterator.next();
System.out.println(" " + entry.getKey() + " -> " + entry.getValue());
if (entry.getValue() == 20) {
mapIterator.remove(); // Safe removal
}
}
System.out.println("After removing value 20: " + map);
// Fail-fast vs Fail-safe iterators
System.out.println("\n=== Fail-Fast Iterator Example ===");
List numbersList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Iterator failFastIterator = numbersList.iterator();
try {
while (failFastIterator.hasNext()) {
Integer num = failFastIterator.next();
System.out.print(num + " ");
if (num == 3) {
numbersList.add(99); // Concurrent modification
}
}
} catch (ConcurrentModificationException e) {
System.out.println("\nConcurrentModificationException caught!");
}
// Using CopyOnWriteArrayList for fail-safe iteration
System.out.println("\n=== Fail-Safe Iterator (CopyOnWriteArrayList) ===");
List copyOnWriteList = new java.util.concurrent.CopyOnWriteArrayList<>(
Arrays.asList(1, 2, 3, 4, 5)
);
Iterator failSafeIterator = copyOnWriteList.iterator();
while (failSafeIterator.hasNext()) {
Integer num = failSafeIterator.next();
System.out.print(num + " ");
if (num == 3) {
copyOnWriteList.add(99); // Modification doesn't affect iterator
}
}
System.out.println("\nOriginal list after iteration: " + copyOnWriteList);
}
}
import java.util.*;
public class ComparatorExamples {
public static void main(String[] args) {
System.out.println("=== Comparator Examples ===");
List names = new ArrayList<>(Arrays.asList(
"John", "Alice", "Bob", "Charlie", "David"
));
// Natural ordering (String implements Comparable)
System.out.println("Original: " + names);
Collections.sort(names);
System.out.println("Natural order: " + names);
// Using Comparator for custom ordering
Collections.sort(names, new ReverseStringComparator());
System.out.println("Reverse order: " + names);
// Using lambda expression (Java 8+)
Collections.sort(names, (a, b) -> a.length() - b.length());
System.out.println("By length: " + names);
// Using Comparator.comparing (Java 8+)
names.sort(Comparator.comparing(String::length));
System.out.println("By length (comparing): " + names);
// Chaining comparators
names.sort(Comparator.comparing(String::length)
.thenComparing(Comparator.naturalOrder()));
System.out.println("By length then natural: " + names);
// Working with custom objects
System.out.println("\n=== Custom Objects Sorting ===");
List employees = Arrays.asList(
new Employee("Alice", "Developer", 75000, 28),
new Employee("Bob", "Manager", 90000, 35),
new Employee("Charlie", "Developer", 80000, 30),
new Employee("David", "Manager", 95000, 40),
new Employee("Eve", "Developer", 70000, 25)
);
System.out.println("Original employees:");
employees.forEach(System.out::println);
// Sort by salary
employees.sort(Comparator.comparingInt(Employee::getSalary));
System.out.println("\nSorted by salary:");
employees.forEach(System.out::println);
// Sort by salary descending
employees.sort(Comparator.comparingInt(Employee::getSalary).reversed());
System.out.println("\nSorted by salary descending:");
employees.forEach(System.out::println);
// Sort by department then salary
employees.sort(Comparator.comparing(Employee::getDepartment)
.thenComparingInt(Employee::getSalary));
System.out.println("\nSorted by department then salary:");
employees.forEach(System.out::println);
// Sort by age then name
employees.sort(Comparator.comparingInt(Employee::getAge)
.thenComparing(Employee::getName));
System.out.println("\nSorted by age then name:");
employees.forEach(System.out::println);
// Handling null values
System.out.println("\n=== Handling Null Values ===");
List namesWithNulls = new ArrayList<>(Arrays.asList(
"John", null, "Alice", null, "Bob", "Charlie"
));
System.out.println("With nulls: " + namesWithNulls);
// Nulls first
namesWithNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("Nulls first: " + namesWithNulls);
// Nulls last
namesWithNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("Nulls last: " + namesWithNulls);
// Using TreeSet with Comparator
System.out.println("\n=== TreeSet with Comparator ===");
Set employeeSet = new TreeSet<>(
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getName)
);
employeeSet.addAll(employees);
System.out.println("TreeSet sorted by department then name:");
employeeSet.forEach(System.out::println);
// Collections utility methods
System.out.println("\n=== Collections Utility Methods ===");
List numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3, 7));
System.out.println("Original: " + numbers);
Collections.reverse(numbers);
System.out.println("Reversed: " + numbers);
Collections.shuffle(numbers);
System.out.println("Shuffled: " + numbers);
Collections.sort(numbers);
System.out.println("Sorted: " + numbers);
System.out.println("Max: " + Collections.max(numbers));
System.out.println("Min: " + Collections.min(numbers));
System.out.println("Frequency of 3: " + Collections.frequency(numbers, 3));
Collections.fill(numbers.subList(0, 3), 0);
System.out.println("After fill: " + numbers);
}
static class ReverseStringComparator implements Comparator {
@Override
public int compare(String s1, String s2) {
return s2.compareTo(s1); // Reverse of natural order
}
}
static class Employee {
private String name;
private String department;
private int salary;
private int age;
public Employee(String name, String department, int salary, int age) {
this.name = name;
this.department = department;
this.salary = salary;
this.age = age;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public int getSalary() { return salary; }
public int getAge() { return age; }
@Override
public String toString() {
return String.format("%-10s %-12s $%-8d %2d",
name, department, salary, age);
}
}
}
| Interface | Methods | Description | Use Cases |
|---|---|---|---|
Iterator |
hasNext()next()remove() |
Forward-only iteration, safe removal | Generic collection iteration |
ListIterator |
hasPrevious()previous()add()set() |
Bidirectional iteration, modification | List traversal in both directions |
Comparator |
compare(T o1, T o2)equals(Object obj) |
Defines custom ordering | Sorting collections, TreeSet/TreeMap |
Collections Utility Methods
sort(List)- Sorts listreverse(List)- Reverses ordershuffle(List)- Randomly permutesswap(List, i, j)- Swaps elementsbinarySearch(List, key)- Searches sorted listmax(Collection)/min(Collection)frequency(Collection, o)- Count occurrencessynchronizedXxx()- Thread-safe wrappersunmodifiableXxx()- Immutable wrappers
Java 8+ Streams with Collections
collection.stream()- Creates streamcollection.parallelStream()- Parallel streamstream.filter()- Filter elementsstream.map()- Transform elementsstream.sorted()- Sort streamstream.collect()- Convert to collectionstream.forEach()- Iterate with lambdastream.reduce()- Aggregate operations
7. Performance Comparison and Best Practices
Understanding the performance characteristics of different collections is crucial for choosing the right data structure for your needs.
Time Complexity Comparison
| Operation | ArrayList | LinkedList | HashSet | TreeSet | HashMap | TreeMap |
|---|---|---|---|---|---|---|
| Add | O(1)* | O(1) | O(1) | O(log n) | O(1) | O(log n) |
| Remove | O(n) | O(n) | O(1) | O(log n) | O(1) | O(log n) |
| Get/Contains | O(1) | O(n) | O(1) | O(log n) | O(1) | O(log n) |
| Insert at Index | O(n) | O(1) | N/A | N/A | N/A | N/A |
| Iteration | O(n) | O(n) | O(n) | O(n) | O(n) | O(n) |
| Memory Overhead | Low | High | Medium | Medium | Medium | Medium |
* Amortized constant time for ArrayList add (resizing may cause O(n))
Collection Best Practices
- Initialize with estimated capacity for ArrayList/HashMap
- Use generics for type safety
- Override equals() and hashCode() for custom objects in HashSet/HashMap
- Implement Comparable for TreeSet/TreeMap elements
- Use Collections utility methods for common operations
- Prefer interface types for variable declarations
- Consider thread safety requirements
Common Mistakes
- Modifying collection during iteration (except with iterator.remove())
- Using raw types (without generics)
- Not overriding equals/hashCode for custom keys in HashMap
- Using Vector/Hashtable when not needed (legacy, synchronized)
- Choosing wrong collection for the task
- Not considering thread safety in multi-threaded environments
- Using null keys/values where not allowed
Choosing the Right Collection
Need duplicates and ordering? → ArrayList/LinkedList
Need unique elements? → HashSet/LinkedHashSet/TreeSet
Need key-value pairs? → HashMap/LinkedHashMap/TreeMap
Need FIFO/LIFO? → ArrayDeque/LinkedList
Need priority ordering? → PriorityQueue
Need thread safety? → ConcurrentHashMap, CopyOnWriteArrayList
Need immutable collections? → Collections.unmodifiableXxx()
- Streams API: Functional-style operations on collections
- Lambda expressions: Cleaner comparator and iteration code
- Method references: Further simplification
- Default methods: New methods in interfaces (forEach, spliterator)
- Concurrent enhancements: Improved concurrent collections
- Collection factory methods: List.of(), Set.of(), Map.of() (Java 9+)
8. Practical Collections Examples
Example 1: E-commerce Shopping Cart
import java.util.*;
public class ShoppingCart {
// Using LinkedHashMap to maintain insertion order
private Map items = new LinkedHashMap<>();
public void addProduct(String productId, String name, double price, int quantity) {
items.merge(productId,
new CartItem(productId, name, price, quantity),
(oldItem, newItem) -> {
oldItem.increaseQuantity(newItem.getQuantity());
return oldItem;
});
}
public void removeProduct(String productId) {
items.remove(productId);
}
public void updateQuantity(String productId, int newQuantity) {
CartItem item = items.get(productId);
if (item != null) {
if (newQuantity <= 0) {
removeProduct(productId);
} else {
item.setQuantity(newQuantity);
}
}
}
public double getTotalPrice() {
return items.values().stream()
.mapToDouble(CartItem::getTotalPrice)
.sum();
}
public List getItemsSortedByPrice() {
List sortedItems = new ArrayList<>(items.values());
sortedItems.sort(Comparator.comparingDouble(CartItem::getPrice).reversed());
return sortedItems;
}
public List getItemsSortedByName() {
List sortedItems = new ArrayList<>(items.values());
sortedItems.sort(Comparator.comparing(CartItem::getName));
return sortedItems;
}
public Map getProductCountByFirstLetter() {
Map countMap = new TreeMap<>();
for (CartItem item : items.values()) {
String firstLetter = item.getName().substring(0, 1).toUpperCase();
countMap.merge(firstLetter, 1, Integer::sum);
}
return countMap;
}
public void displayCart() {
System.out.println("\n=== Shopping Cart ===");
System.out.println("Items in cart:");
items.forEach((id, item) ->
System.out.printf(" %-20s $%-8.2f x %d = $%.2f\n",
item.getName(), item.getPrice(),
item.getQuantity(), item.getTotalPrice()));
System.out.println("Total: $" + getTotalPrice());
// Show statistics
System.out.println("\nCart Statistics:");
System.out.println("Total items: " + items.size());
System.out.println("Total quantity: " +
items.values().stream().mapToInt(CartItem::getQuantity).sum());
// Most expensive item
items.values().stream()
.max(Comparator.comparingDouble(CartItem::getPrice))
.ifPresent(item ->
System.out.println("Most expensive: " + item.getName() +
" ($" + item.getPrice() + ")"));
}
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Add products
cart.addProduct("P001", "Laptop", 999.99, 1);
cart.addProduct("P002", "Mouse", 25.50, 2);
cart.addProduct("P003", "Keyboard", 75.00, 1);
cart.addProduct("P001", "Laptop", 999.99, 1); // Add more of same
// Display cart
cart.displayCart();
// Update quantity
cart.updateQuantity("P002", 3);
// Remove product
cart.removeProduct("P003");
System.out.println("\n=== After Updates ===");
cart.displayCart();
// Get sorted items
System.out.println("\n=== Items Sorted by Price (High to Low) ===");
cart.getItemsSortedByPrice().forEach(System.out::println);
System.out.println("\n=== Product Count by First Letter ===");
cart.getProductCountByFirstLetter().forEach((letter, count) ->
System.out.println(letter + ": " + count + " products"));
}
}
class CartItem {
private String productId;
private String name;
private double price;
private int quantity;
public CartItem(String productId, String name, double price, int quantity) {
this.productId = productId;
this.name = name;
this.price = price;
this.quantity = quantity;
}
public double getTotalPrice() {
return price * quantity;
}
public void increaseQuantity(int amount) {
this.quantity += amount;
}
// Getters and setters
public String getProductId() { return productId; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
@Override
public String toString() {
return String.format("%-15s $%-8.2f x %d", name, price, quantity);
}
}
Example 2: Employee Management System
import java.util.*;
import java.util.stream.Collectors;
public class EmployeeManagementSystem {
private Map employees = new HashMap<>();
private Map departments = new HashMap<>();
public void addEmployee(Employee emp) {
employees.put(emp.getId(), emp);
// Update department employee count
departments.computeIfAbsent(emp.getDepartment(),
k -> new Department(k)).addEmployee(emp);
}
public void removeEmployee(int id) {
Employee emp = employees.remove(id);
if (emp != null) {
departments.get(emp.getDepartment()).removeEmployee(emp);
}
}
public Employee findEmployeeById(int id) {
return employees.get(id);
}
public List findEmployeesByName(String name) {
return employees.values().stream()
.filter(e -> e.getName().toLowerCase()
.contains(name.toLowerCase()))
.collect(Collectors.toList());
}
public Map> getEmployeesByDepartment() {
return employees.values().stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
}
public Map getAverageSalaryByDepartment() {
return employees.values().stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
}
public Map getEmployeeCountByDepartment() {
return employees.values().stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.counting()
));
}
public List getTopEarners(int n) {
return employees.values().stream()
.sorted(Comparator.comparingDouble(Employee::getSalary)
.reversed())
.limit(n)
.collect(Collectors.toList());
}
public Map getHighestPaidByDepartment() {
return employees.values().stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator
.comparingDouble(Employee::getSalary)),
Optional::get
)
));
}
public Set getUniqueJobTitles() {
return employees.values().stream()
.map(Employee::getJobTitle)
.collect(Collectors.toCollection(TreeSet::new));
}
public void generateReport() {
System.out.println("\n=== Employee Management System Report ===");
System.out.println("Total Employees: " + employees.size());
System.out.println("\n=== Employees by Department ===");
getEmployeesByDepartment().forEach((dept, empList) -> {
System.out.println("\n" + dept + " (" + empList.size() + " employees):");
empList.forEach(e -> System.out.println(" " + e));
});
System.out.println("\n=== Department Statistics ===");
getAverageSalaryByDepartment().forEach((dept, avgSalary) ->
System.out.printf("%-15s: Average Salary: $%,.2f\n", dept, avgSalary));
System.out.println("\n=== Top 5 Earners ===");
getTopEarners(5).forEach(e ->
System.out.printf(" %-20s $%,.2f\n", e.getName(), e.getSalary()));
System.out.println("\n=== Highest Paid by Department ===");
getHighestPaidByDepartment().forEach((dept, emp) ->
System.out.printf(" %-15s: %-20s $%,.2f\n",
dept, emp.getName(), emp.getSalary()));
System.out.println("\n=== Unique Job Titles ===");
getUniqueJobTitles().forEach(title -> System.out.println(" " + title));
}
public static void main(String[] args) {
EmployeeManagementSystem ems = new EmployeeManagementSystem();
// Add sample employees
ems.addEmployee(new Employee(101, "Alice Johnson", "Engineering",
"Software Engineer", 85000, 28));
ems.addEmployee(new Employee(102, "Bob Smith", "Engineering",
"Senior Engineer", 110000, 35));
ems.addEmployee(new Employee(103, "Charlie Brown", "Sales",
"Sales Manager", 95000, 40));
ems.addEmployee(new Employee(104, "Diana Prince", "Sales",
"Sales Representative", 65000, 28));
ems.addEmployee(new Employee(105, "Eve Adams", "HR",
"HR Manager", 90000, 38));
ems.addEmployee(new Employee(106, "Frank Miller", "Engineering",
"Software Engineer", 88000, 30));
ems.addEmployee(new Employee(107, "Grace Lee", "Marketing",
"Marketing Specialist", 75000, 32));
ems.addEmployee(new Employee(108, "Henry Ford", "Engineering",
"Lead Engineer", 125000, 42));
// Generate report
ems.generateReport();
// Search examples
System.out.println("\n=== Search Results ===");
System.out.println("Employees with 'Smith':");
ems.findEmployeesByName("Smith").forEach(System.out::println);
System.out.println("\nEmployee with ID 103:");
System.out.println(ems.findEmployeeById(103));
// Remove an employee
ems.removeEmployee(104);
System.out.println("\n=== After removing employee 104 ===");
System.out.println("Total employees: " + ems.employees.size());
}
}
class Employee {
private int id;
private String name;
private String department;
private String jobTitle;
private double salary;
private int age;
public Employee(int id, String name, String department,
String jobTitle, double salary, int age) {
this.id = id;
this.name = name;
this.department = department;
this.jobTitle = jobTitle;
this.salary = salary;
this.age = age;
}
// Getters
public int getId() { return id; }
public String getName() { return name; }
public String getDepartment() { return department; }
public String getJobTitle() { return jobTitle; }
public double getSalary() { return salary; }
public int getAge() { return age; }
@Override
public String toString() {
return String.format("%-20s %-15s %-20s $%,.2f %2dy",
name, department, jobTitle, salary, age);
}
}
class Department {
private String name;
private Set employees = new HashSet<>();
public Department(String name) {
this.name = name;
}
public void addEmployee(Employee emp) {
employees.add(emp);
}
public void removeEmployee(Employee emp) {
employees.remove(emp);
}
public String getName() { return name; }
public Set getEmployees() { return employees; }
}
9. Collections Framework Best Practices
- Choosing wrong collection type for the task
- Not overriding equals() and hashCode() for custom objects in HashSet/HashMap
- Modifying collections during iteration (except with iterator.remove())
- Using raw types instead of generics
- Not considering thread safety in multi-threaded applications
- Inefficient operations (e.g., contains() on ArrayList instead of HashSet)
- Not setting initial capacity for large collections
Collections Best Practices
- Use generics for type safety
- Initialize collections with estimated capacity
- Prefer interface types (List, Set, Map) for variables
- Use Collections utility methods for common operations
- Consider performance characteristics when choosing collections
- Use immutable collections when data shouldn't change
- Implement Comparable for objects in TreeSet/TreeMap
Performance Optimization Tips
- Use
ArrayListfor random access,LinkedListfor frequent insertions/deletions - Use
HashSet/HashMapfor O(1) lookups - Avoid frequent resizing by setting initial capacity
- Use parallel streams for large collections on multi-core systems
- Consider ConcurrentHashMap for concurrent access
- Use primitive collections (Eclipse Collections, fastutil) for primitive types
Choosing Collections - Quick Reference
For Lists:
- ArrayList: Default choice, random access
- LinkedList: Frequent insertions/deletions
- CopyOnWriteArrayList: Thread-safe, read-heavy
- Vector: Legacy, synchronized
For Sets/Maps:
- HashSet/HashMap: Default, O(1) operations
- LinkedHashSet/LinkedHashMap: Insertion/access order
- TreeSet/TreeMap: Sorted, O(log n) operations
- ConcurrentHashMap: Thread-safe map
Java 9 introduced convenient factory methods for creating immutable collections:
List.of("a", "b", "c")- Immutable listSet.of("a", "b", "c")- Immutable setMap.of("key1", 1, "key2", 2)- Immutable mapMap.ofEntries(entry("k1", 1), entry("k2", 2))- More entries- Note: These collections are immutable (cannot add/remove)