Iterator, Comparable và Comparator
Sau bài này, bạn sẽ:
- Hiểu Iterator interface và safe removal với
iter.remove()tránh ConcurrentModificationException - Phân biệt Comparable (natural ordering trong class) vs Comparator (custom ordering riêng biệt)
- Sử dụng Comparator methods (Java 8+):
comparing(),thenComparing(),reversed(),nullsFirst() - Implement Comparable với
compareTo()và Comparator vớicompare() - Áp dụng cho sorting, multi-field comparison, và null-safe ordering
Bài trước: Queue và Deque — Đã học về Queue, Deque, PriorityQueue, ArrayDeque. Bài này sẽ tìm hiểu Iterator, Comparable và Comparator - công cụ để duyệt và sắp xếp collections.
Iterator Interface
Iterator là một interface cho phép duyệt qua các phần tử của một collection theo thứ tự.
Iterator Methods
public interface Iterator<E> {
boolean hasNext(); // Còn phần tử tiếp theo?
E next(); // Lấy phần tử tiếp theo
void remove(); // Xóa phần tử hiện tại (optional)
}
Basic Usage
import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
// Get iterator
Iterator<String> iterator = fruits.iterator();
// Iterate using while loop
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
}
}
Safe Removal with Iterator
public class SafeRemoval {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// ❌ WRONG: ConcurrentModificationException
// for (Integer num : numbers) {
// if (num % 2 == 0) {
// numbers.remove(num); // THROWS EXCEPTION!
// }
// }
// ✅ CORRECT: Use Iterator.remove()
Iterator<Integer> iter = numbers.iterator();
while (iter.hasNext()) {
Integer num = iter.next();
if (num % 2 == 0) {
iter.remove(); // Safe removal
}
}
System.out.println(numbers); // [1, 3, 5, 7, 9]
}
}
ConcurrentModificationException
ConcurrentModificationException xảy ra khi bạn modify (thêm/xóa) collection trong khi đang iterate nó (không qua Iterator).
ConcurrentModificationException Sequence Diagram
Giải thích:
- Iterator tạo → snapshot expectedModCount = modCount hiện tại
- Mỗi lần next() → check modCount == expectedModCount
- Nếu list.add/remove (không qua Iterator) → modCount++ nhưng expectedModCount không đổi
- Lần next() tiếp theo → mismatch → Exception!
Cơ chế hoạt động của ConcurrentModificationException
Ẩn dụ (Metaphor): Tưởng tượng bạn đang đọc một cuốn sách (book) có page counter (trang hiện tại). Trước khi đọc, bạn ghi lại số trang hiện tại vào một tờ giấy note (snapshot). Mỗi khi ai đó thêm/xóa trang trong cuốn sách, page counter tăng. Khi bạn muốn lật trang, bạn kiểm tra lại: nếu số trang trên tờ giấy note khác với page counter thực tế → có người đã sửa sách → bạn không thể tiếp tục đọc an toàn → throw exception!
Collections có một modification count (modCount) để track số lần collection bị modify. Iterator lưu lại modCount khi được tạo. Khi gọi next(), Iterator kiểm tra modCount. Nếu khác → throw exception.
Chi tiết cơ chế trong AbstractList
// Simplified version of AbstractList
abstract class AbstractList<E> {
protected transient int modCount = 0; // Modification counter
public boolean add(E e) {
modCount++; // Mỗi lần modify → tăng modCount
// ... add logic
}
public E remove(int index) {
modCount++; // Mỗi lần modify → tăng modCount
// ... remove logic
}
}
// Iterator implementation
private class Itr implements Iterator<E> {
int cursor = 0; // Index of next element to return
int lastRet = -1; // Index of last element returned; -1 if no such
int expectedModCount = modCount; // Snapshot of modCount at creation time
public E next() {
checkForComodification(); // Check BEFORE every operation
// ... return next element
}
public void remove() {
checkForComodification();
// ... remove element
expectedModCount = modCount; // Update expectedModCount after safe removal
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Giải thích:
modCount: Global counter trong ArrayList/LinkedList, tăng mỗi khi có add/remove/clearexpectedModCount: Local snapshot trong mỗi Iterator, lưu giá trị modCount tại thời điểm tạo IteratorcheckForComodification(): So sánhmodCount == expectedModCount. Nếu khác → có modification từ bên ngoài Iterator → exception!
Minh họa với for-each loop
import java.util.*;
public class ConcurrentModificationMechanism {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// For-each loop tạo Iterator ngầm định
try {
for (String item : list) {
System.out.println("Current item: " + item);
if (item.equals("B")) {
list.remove(item); // modCount++
// Lần gọi next() tiếp theo: modCount != expectedModCount → BOOM!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Exception caught: " + e.getClass().getSimpleName());
}
// Output:
// Current item: A
// Current item: B
// Exception caught: ConcurrentModificationException
// Tại sao?
// 1. Iterator tạo với expectedModCount = 0
// 2. Lần 1: item = "A", modCount = 0, OK
// 3. Lần 2: item = "B", modCount = 0, OK
// 4. list.remove("B") → modCount = 1
// 5. Lần 3: iter.next() gọi checkForComodification()
// → modCount (1) != expectedModCount (0) → Exception!
}
}
Tại sao xảy ra?
public class ConcurrentModificationDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// ❌ CASE 1: Remove trong for-each loop
try {
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Exception in for-each: " + e.getClass().getSimpleName());
}
// ❌ CASE 2: Add trong for-each loop
try {
for (String item : list) {
if (item.equals("C")) {
list.add("E"); // ConcurrentModificationException!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Exception when adding: " + e.getClass().getSimpleName());
}
// ✅ SOLUTION 1: Dùng Iterator.remove()
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals("B")) {
iter.remove(); // OK
}
}
// ✅ SOLUTION 2: Collect items to remove, then remove after iteration
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (item.equals("C")) {
toRemove.add(item);
}
}
list.removeAll(toRemove);
// ✅ SOLUTION 3: Dùng removeIf() (Java 8+)
list.removeIf(item -> item.equals("D"));
System.out.println("Final list: " + list);
}
}
Multiple Iterators - Mỗi Iterator có snapshot riêng
Ẩn dụ: Tưởng tượng 3 người cùng đọc cùng 1 cuốn sách (shared book). Mỗi người có 1 tờ giấy note (iterator) ghi lại page counter hiện tại (expectedModCount). Người A lật trang (modify qua Iterator.remove()) → page counter thay đổi → tờ giấy note của A được update → OK. Nhưng tờ giấy note của B và C vẫn giữ giá trị cũ → khi B hoặc C muốn lật trang → phát hiện page counter đã khác → exception!
import java.util.*;
public class MultipleIterators {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
Iterator<String> iter1 = list.iterator();
Iterator<String> iter2 = list.iterator();
Iterator<String> iter3 = list.iterator();
// Mỗi iterator có expectedModCount snapshot riêng (tất cả = 0 lúc này)
System.out.println("iter1 first: " + iter1.next()); // A
System.out.println("iter2 first: " + iter2.next()); // A
System.out.println("iter3 first: " + iter3.next()); // A
// iter1 modify qua safe remove
iter1.next(); // B
iter1.remove(); // Remove "B", modCount++, iter1 updates its expectedModCount
// iter1 vẫn hoạt động bình thường (expectedModCount đã được update)
System.out.println("iter1 continues: " + iter1.next()); // C
// iter2 và iter3 CÒN GIỮ expectedModCount CŨ!
try {
System.out.println("iter2 next: " + iter2.next()); // ConcurrentModificationException!
} catch (ConcurrentModificationException e) {
System.out.println("iter2 crashed: modCount changed!");
}
try {
System.out.println("iter3 next: " + iter3.next()); // ConcurrentModificationException!
} catch (ConcurrentModificationException e) {
System.out.println("iter3 crashed: modCount changed!");
}
}
}
// Output:
// iter1 first: A
// iter2 first: A
// iter3 first: A
// iter1 continues: C
// iter2 crashed: modCount changed!
// iter3 crashed: modCount changed!
Kết luận: Mỗi Iterator có expectedModCount snapshot độc lập. Chỉ Iterator thực hiện modification mới update được expectedModCount của chính nó. Các Iterator khác vẫn giữ snapshot cũ → crash khi gọi next().
ListIterator.set() và add() an toàn trong khi iterate
ListIterator extends Iterator và có thêm set() và add() methods. Khác với list.add() thông thường, ListIterator.set() và ListIterator.add() update expectedModCount internally nên không gây ConcurrentModificationException!
import java.util.*;
public class ListIteratorSafeModification {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// ❌ Iterator.remove() - chỉ xóa được, không add/set được
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals("B")) {
iter.remove(); // OK - safe removal
// iter.add("X"); // COMPILE ERROR - Iterator không có add()
}
}
System.out.println("After Iterator: " + list); // [A, C, D]
// ✅ ListIterator.set() và add() - an toàn
list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
ListIterator<String> listIter = list.listIterator();
while (listIter.hasNext()) {
String item = listIter.next();
if (item.equals("B")) {
listIter.set("B-MODIFIED"); // Replace "B" with "B-MODIFIED"
}
if (item.equals("C")) {
listIter.add("C-INSERTED"); // Insert "C-INSERTED" after "C"
}
}
System.out.println("After ListIterator: " + list); // [A, B-MODIFIED, C, C-INSERTED, D]
// So sánh: list.add() vs ListIterator.add()
list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> iter2 = list.listIterator();
iter2.next(); // A
iter2.add("X"); // ListIterator.add() - updates expectedModCount internally
System.out.println("After listIter.add(): " + iter2.next()); // B - OK!
// Nhưng nếu dùng list.add() thông thường:
list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter3 = list.iterator();
iter3.next(); // A
list.add("X"); // list.add() - modCount++ nhưng KHÔNG update expectedModCount!
try {
System.out.println("After list.add(): " + iter3.next()); // ConcurrentModificationException!
} catch (ConcurrentModificationException e) {
System.out.println("list.add() caused exception!");
}
}
}
Tóm lại:
Iterator.remove(): An toàn, xóa phần tử hiện tạiListIterator.set(): An toàn, replace phần tử hiện tạiListIterator.add(): An toàn, insert phần tử mới trước vị trí hiện tạilist.add(),list.remove(): KHÔNG AN TOÀN khi đang iterate → ConcurrentModificationException
For-each loop tạo Iterator ngầm định. Khi bạn modify collection qua list.remove(), modCount thay đổi nhưng Iterator không biết → exception!
// For-each loop này:
for (String item : list) {
// ...
}
// Thực chất là:
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
// ...
}
Cách tránh ConcurrentModificationException
| Phương pháp | Code | Note |
|---|---|---|
| Iterator.remove() | iter.remove() | Chỉ xóa được, không add được |
| Collect then remove | list.removeAll(toRemove) | Work cho cả add và remove |
| removeIf() (Java 8+) | list.removeIf(predicate) | Ngắn gọn, chỉ cho remove |
| Stream filter (Java 8+) | list.stream().filter(...) | Tạo list mới |
| Traditional for loop | for (int i = 0; ...) | Cần cẩn thận với index |
Iterable Interface
Iterable là interface cha của Collection, cho phép object được dùng trong for-each loop.
public interface Iterable<T> {
Iterator<T> iterator();
// Java 8+
default void forEach(Consumer<? super T> action) {
for (T t : this) {
action.accept(t);
}
}
}
Custom Iterable Class
class Range implements Iterable<Integer> {
private int start;
private int end;
public Range(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
private int current = start;
@Override
public boolean hasNext() {
return current < end;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return current++;
}
};
}
public static void main(String[] args) {
Range range = new Range(1, 6);
// Can use for-each loop
for (int num : range) {
System.out.print(num + " ");
}
// Output: 1 2 3 4 5
}
}
Comparable Interface - Natural Ordering
Comparable interface cho phép object tự định nghĩa natural ordering (thứ tự tự nhiên) của nó.
public interface Comparable<T> {
int compareTo(T o);
// Return: negative if this < o
// zero if this == o
// positive if this > o
}
Implementing Comparable
class Student implements Comparable<Student> {
String name;
int id;
double gpa;
public Student(String name, int id, double gpa) {
this.name = name;
this.id = id;
this.gpa = gpa;
}
@Override
public int compareTo(Student other) {
// Natural order: sort by ID
return Integer.compare(this.id, other.id);
}
@Override
public String toString() {
return name + "(ID:" + id + ", GPA:" + gpa + ")";
}
}
public class ComparableDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Charlie", 103, 3.5));
students.add(new Student("Alice", 101, 3.8));
students.add(new Student("Bob", 102, 3.9));
// Sort using natural order (by ID)
Collections.sort(students);
// Hoặc: students.sort(null);
System.out.println("Sorted by ID:");
students.forEach(System.out::println);
// Output:
// Alice(ID:101, GPA:3.8)
// Bob(ID:102, GPA:3.9)
// Charlie(ID:103, GPA:3.5)
}
}
compareTo() Contract
Quan trọng: compareTo() phải consistent với equals():
// Nếu a.equals(b) == true, thì a.compareTo(b) == 0
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return id == student.id;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
// ✅ ĐÚNG: Dùng Integer.compare(), Double.compare(), ...
return Integer.compare(this.age, other.age);
// ❌ NGUY HIỂM: Trừ trực tiếp có thể overflow!
return this.age - other.age; // Nếu this.age = -2B, other.age = 2B → overflow!
// ✅ ĐÚNG: So sánh String
return this.name.compareTo(other.name);
Comparator Interface - Custom Ordering
Comparator là một separate object để định nghĩa custom ordering, không thay đổi class gốc.
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// Return: negative if o1 < o2
// zero if o1 == o2
// positive if o1 > o2
}
Basic Comparator Usage
import java.util.*;
class Student {
String name;
int id;
double gpa;
public Student(String name, int id, double gpa) {
this.name = name;
this.id = id;
this.gpa = gpa;
}
@Override
public String toString() {
return name + "(GPA:" + gpa + ")";
}
}
public class ComparatorDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Charlie", 103, 3.5));
students.add(new Student("Alice", 101, 3.8));
students.add(new Student("Bob", 102, 3.9));
// Comparator 1: Sort by name
Comparator<Student> byName = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareTo(s2.name);
}
};
Collections.sort(students, byName);
System.out.println("By name: " + students);
// Comparator 2: Sort by GPA (descending)
Comparator<Student> byGpaDesc = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return Double.compare(s2.gpa, s1.gpa); // Reverse order
}
};
Collections.sort(students, byGpaDesc);
System.out.println("By GPA (desc): " + students);
}
}
Comparator với Lambda (Java 8+)
public class ComparatorLambda {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Charlie", 103, 3.5));
students.add(new Student("Alice", 101, 3.8));
students.add(new Student("Bob", 102, 3.9));
// Lambda expressions
students.sort((s1, s2) -> s1.name.compareTo(s2.name));
System.out.println("By name: " + students);
students.sort((s1, s2) -> Double.compare(s2.gpa, s1.gpa));
System.out.println("By GPA desc: " + students);
students.sort((s1, s2) -> Integer.compare(s1.id, s2.id));
System.out.println("By ID: " + students);
}
}
Comparator Chaining Deep Dive - Thứ tự quan trọng!
Ẩn dụ: Tưởng tượng sắp xếp danh sách học sinh như sắp xếp sách trong thư viện. Tiêu chí 1: Theo tên tác giả (author). Tiêu chí 2: Theo năm xuất bản (year). Nếu bạn "đảo ngược" (reverse) toàn bộ hệ thống sắp xếp → cả author VÀ year đều đảo ngược, không chỉ riêng year!
Comparator Chaining Flowchart
Ví dụ:
Comparator.comparing(Person::getDept) // field1
.thenComparingInt(Person::getSalary) // field2 (nếu field1 bằng)
.thenComparing(Person::getName) // field3 (nếu field2 bằng)
reversed() applies to ENTIRE chain, not just last field!
import java.util.*;
class Book {
String title;
String author;
int year;
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
@Override
public String toString() {
return title + " by " + author + " (" + year + ")";
}
}
public class ComparatorChainingDemo {
public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("Java Basics", "Alice", 2020),
new Book("Python Guide", "Alice", 2019),
new Book("Data Structures", "Bob", 2020),
new Book("Algorithms", "Bob", 2018)
);
// ❌ NHẦM TƯ TƯỞNG: reversed() chỉ reverse year?
// THỰC TẾ: reversed() reverse TOÀN BỘ comparator chain!
books.sort(
Comparator.comparing(Book::getAuthor)
.thenComparingInt(Book::getYear)
.reversed() // Đảo ngược CẢ author VÀ year!
);
System.out.println("=== Reversed entire chain ===");
books.forEach(System.out::println);
// Output (author DESC, year DESC):
// Data Structures by Bob (2020)
// Algorithms by Bob (2018)
// Java Basics by Alice (2020)
// Python Guide by Alice (2019)
// ✅ ĐÚNG: Muốn chỉ reverse year → dùng Comparator.reverseOrder()
books.sort(
Comparator.comparing(Book::getAuthor)
.thenComparing(Book::getYear, Comparator.reverseOrder())
);
System.out.println("\n=== Author ASC, Year DESC ===");
books.forEach(System.out::println);
// Output (author ASC, year DESC):
// Java Basics by Alice (2020)
// Python Guide by Alice (2019)
// Data Structures by Bob (2020)
// Algorithms by Bob (2018)
// Hoặc dùng negative comparingInt
books.sort(
Comparator.comparing(Book::getAuthor)
.thenComparingInt(b -> -b.year) // Negative for descending
);
System.out.println("\n=== Author ASC, Year DESC (negative trick) ===");
books.forEach(System.out::println);
}
}
Câu hỏi: Đoạn code sau sắp xếp theo thứ tự nào?
list.sort(Comparator.comparing(Employee::getDepartment)
.thenComparingInt(Employee::getSalary)
.reversed());
Câu trả lời:
- Department DESC, Salary DESC (đảo ngược TOÀN BỘ)
- KHÔNG PHẢI Department ASC, Salary DESC!
nullsFirst() và nullsLast() - Wrapping pattern
nullsFirst() và nullsLast() wrap một Comparator khác để xử lý null values.
import java.util.*;
public class NullHandlingComparator {
public static void main(String[] args) {
List<String> list = Arrays.asList("Charlie", null, "Alice", "Bob", null, "David");
// nullsFirst(naturalOrder()) - null elements đầu tiên
list.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("nullsFirst: " + list);
// Output: [null, null, Alice, Bob, Charlie, David]
// nullsLast(naturalOrder()) - null elements cuối cùng
list = Arrays.asList("Charlie", null, "Alice", "Bob", null, "David");
list.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("nullsLast: " + list);
// Output: [Alice, Bob, Charlie, David, null, null]
// Combine với reversed()
list = Arrays.asList("Charlie", null, "Alice", "Bob", null, "David");
list.sort(Comparator.nullsLast(Comparator.reverseOrder()));
System.out.println("nullsLast + reverseOrder: " + list);
// Output: [David, Charlie, Bob, Alice, null, null]
// Custom objects với null-safe comparison
List<Book> books = Arrays.asList(
new Book("Java", "Alice", 2020),
null,
new Book("Python", "Bob", 2019),
null
);
books.sort(Comparator.nullsFirst(Comparator.comparing(Book::getAuthor)));
System.out.println("\nBooks with nullsFirst:");
books.forEach(b -> System.out.println(b == null ? "null" : b));
// Output:
// null
// null
// Java by Alice (2020)
// Python by Bob (2019)
}
}
Wrapping pattern giải thích:
// nullsFirst(comparator) tạo một wrapper Comparator:
Comparator<String> comparator = Comparator.nullsFirst(Comparator.naturalOrder());
// Equivalent to:
Comparator<String> comparator = (a, b) -> {
if (a == null && b == null) return 0;
if (a == null) return -1; // null comes first
if (b == null) return 1; // null comes first
return a.compareTo(b); // Delegate to naturalOrder()
};
Comparator Static Methods (Java 8+)
Java 8 cung cấp nhiều static methods tiện lợi trong Comparator:
import java.util.*;
public class ComparatorMethods {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Charlie", 103, 3.5));
students.add(new Student("Alice", 101, 3.8));
students.add(new Student("Bob", 102, 3.9));
students.add(new Student("Alice", 104, 3.6));
// comparing() - Sort by single field
students.sort(Comparator.comparing(s -> s.name));
// Hoặc method reference:
students.sort(Comparator.comparing(Student::getName));
// comparingInt(), comparingDouble() - Tối ưu cho primitive
students.sort(Comparator.comparingInt(s -> s.id));
students.sort(Comparator.comparingDouble(s -> s.gpa));
// reversed() - Đảo ngược thứ tự
students.sort(Comparator.comparing(Student::getGpa).reversed());
System.out.println("By GPA desc: " + students);
// thenComparing() - Secondary sort
students.sort(
Comparator.comparing(Student::getName)
.thenComparingDouble(Student::getGpa)
);
System.out.println("By name, then GPA: " + students);
// thenComparing() chain
students.sort(
Comparator.comparing(Student::getName)
.thenComparingDouble(Student::getGpa)
.thenComparingInt(Student::getId)
);
// naturalOrder() và reverseOrder()
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
numbers.sort(Comparator.naturalOrder());
System.out.println("Natural: " + numbers); // [1, 2, 5, 8, 9]
numbers.sort(Comparator.reverseOrder());
System.out.println("Reverse: " + numbers); // [9, 8, 5, 2, 1]
// nullsFirst() và nullsLast()
List<String> withNulls = Arrays.asList("B", null, "A", null, "C");
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("Nulls first: " + withNulls); // [null, null, A, B, C]
withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("Nulls last: " + withNulls); // [A, B, C, null, null]
}
}
// Getters cho Student class
class Student {
private String name;
private int id;
private double gpa;
// Constructor và toString như trước
public String getName() { return name; }
public int getId() { return id; }
public double getGpa() { return gpa; }
}
Comparable vs Comparator
| Đặc điểm | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | int compareTo(T o) | int compare(T o1, T o2) |
| Modify class | Phải modify class gốc | Không cần modify class gốc |
| Number of sorts | Chỉ 1 natural order | Nhiều custom orders |
| Use case | Default/natural ordering | Custom/multiple orderings |
| Sorting | Collections.sort(list) | Collections.sort(list, comparator) |
// Comparable - natural ordering (chỉ 1 cách sort)
class Student implements Comparable<Student> {
int id;
@Override
public int compareTo(Student other) {
return Integer.compare(this.id, other.id);
}
}
// Sort với natural order
Collections.sort(students);
// ---
// Comparator - custom orderings (nhiều cách sort)
Comparator<Student> byName = Comparator.comparing(Student::getName);
Comparator<Student> byGpa = Comparator.comparingDouble(Student::getGpa);
Comparator<Student> byGpaDesc = Comparator.comparingDouble(Student::getGpa).reversed();
// Sort với custom order
Collections.sort(students, byName);
Collections.sort(students, byGpa);
Collections.sort(students, byGpaDesc);
- Comparable: Khi có một cách sort "tự nhiên" rõ ràng (ID, alphabetical, chronological)
- Comparator: Khi cần nhiều cách sort khác nhau hoặc không thể modify class gốc
Collections.sort() vs List.sort()
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9));
// Cách 1: Collections.sort() (Java 1.2+)
Collections.sort(numbers);
// Cách 2: List.sort() (Java 8+) - PREFERRED
numbers.sort(null); // Natural order
numbers.sort(Comparator.naturalOrder());
numbers.sort(Comparator.reverseOrder());
numbers.sort(Comparator.comparing(Integer::intValue));
// Với Comparator
Collections.sort(students, byName); // Old style
students.sort(byName); // New style (Java 8+)
Dùng List.sort() (Java 8+) thay vì Collections.sort()
// ✅ Modern (Java 8+)
list.sort(comparator);
// ❌ Old style
Collections.sort(list, comparator);
Spliterator - Hỗ trợ Parallel Iteration cho Streams
Spliterator (Splitable Iterator) là interface đặc biệt được thiết kế cho parallel processing trong Stream API (Java 8+). Nó có khả năng split (chia nhỏ) collection để xử lý song song.
Ẩn dụ: Tưởng tượng bạn có một chồng sách cần đóng gói. Với Iterator truyền thống, bạn đóng gói từng cuốn một (sequential). Với Spliterator, bạn có thể chia chồng sách làm nhiều phần → gọi thêm bạn bè (threads) → mỗi người đóng gói một phần → nhanh hơn (parallel processing)!
Spliterator trySplit Diagram
Parallel Processing: Collection được split thành nhiều phần → mỗi phần xử lý bởi thread riêng → nhanh hơn sequential iteration.
Spliterator Characteristics
import java.util.*;
public class SpliteratorDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
// Lấy Spliterator từ collection
Spliterator<Integer> spliterator = numbers.spliterator();
// Characteristics - đặc tính của Spliterator
System.out.println("Characteristics:");
System.out.println("ORDERED: " + spliterator.hasCharacteristics(Spliterator.ORDERED)); // true
System.out.println("SIZED: " + spliterator.hasCharacteristics(Spliterator.SIZED)); // true
System.out.println("SUBSIZED: " + spliterator.hasCharacteristics(Spliterator.SUBSIZED)); // true
System.out.println("SORTED: " + spliterator.hasCharacteristics(Spliterator.SORTED)); // false
System.out.println("DISTINCT: " + spliterator.hasCharacteristics(Spliterator.DISTINCT)); // false
System.out.println("IMMUTABLE: " + spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); // false
System.out.println("NONNULL: " + spliterator.hasCharacteristics(Spliterator.NONNULL)); // false
// Estimated size
System.out.println("\nEstimated size: " + spliterator.estimateSize()); // 8
// trySplit() - chia Spliterator thành 2 phần
Spliterator<Integer> split1 = numbers.spliterator();
Spliterator<Integer> split2 = split1.trySplit(); // Split thành 2 nửa
System.out.println("\nAfter split:");
System.out.print("split1: ");
split1.forEachRemaining(n -> System.out.print(n + " ")); // 5 6 7 8
System.out.print("\nsplit2: ");
split2.forEachRemaining(n -> System.out.print(n + " ")); // 1 2 3 4
}
}
Spliterator Characteristics Explained
| Characteristic | Ý nghĩa | Ví dụ |
|---|---|---|
| ORDERED | Elements có thứ tự cố định | List, LinkedHashSet |
| SORTED | Elements đã được sắp xếp | TreeSet, TreeMap |
| SIZED | Biết trước size chính xác | ArrayList, HashSet |
| SUBSIZED | Split ra cũng biết size | ArrayList |
| DISTINCT | Không có duplicate | Set implementations |
| IMMUTABLE | Không thể modify | List.of(), Set.of() |
| NONNULL | Không có null elements | List.of(), Set.of() |
| CONCURRENT | Thread-safe concurrent modification | ConcurrentHashMap |
trySplit() cho Parallel Processing
import java.util.*;
public class SpliteratorParallelDemo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 16; i++) {
numbers.add(i);
}
Spliterator<Integer> spliterator = numbers.spliterator();
// Split recursive để demo parallel processing
System.out.println("Original size: " + spliterator.estimateSize()); // 16
Spliterator<Integer> split1 = spliterator.trySplit(); // [1..8]
System.out.println("After 1st split - remaining: " + spliterator.estimateSize() + ", split: " + split1.estimateSize());
Spliterator<Integer> split2 = spliterator.trySplit(); // [9..12]
System.out.println("After 2nd split - remaining: " + spliterator.estimateSize() + ", split: " + split2.estimateSize());
Spliterator<Integer> split3 = split1.trySplit(); // [1..4]
System.out.println("After 3rd split - split1 remaining: " + split1.estimateSize() + ", split: " + split3.estimateSize());
// Giờ có 4 phần: split3 [1..4], split1 [5..8], split2 [9..12], spliterator [13..16]
// Có thể xử lý song song 4 phần này!
System.out.println("\nProcessing parts:");
System.out.print("Part 1: "); split3.forEachRemaining(n -> System.out.print(n + " "));
System.out.print("\nPart 2: "); split1.forEachRemaining(n -> System.out.print(n + " "));
System.out.print("\nPart 3: "); split2.forEachRemaining(n -> System.out.print(n + " "));
System.out.print("\nPart 4: "); spliterator.forEachRemaining(n -> System.out.print(n + " "));
}
}
Spliterator và Stream API
Spliterator là engine đằng sau parallelStream() của Java 8!
import java.util.*;
import java.util.stream.*;
public class SpliteratorStreamDemo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
numbers.add(i);
}
// Sequential stream - dùng Spliterator.forEachRemaining() internally
long sum1 = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sequential sum: " + sum1);
// Parallel stream - dùng Spliterator.trySplit() để chia nhỏ và process song song
long sum2 = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Parallel sum: " + sum2);
// Custom Spliterator example - tạo stream từ Spliterator
Spliterator<Integer> spliterator = numbers.spliterator();
Stream<Integer> stream = StreamSupport.stream(spliterator, false); // false = sequential
long count = stream.filter(n -> n % 2 == 0).count();
System.out.println("Even numbers count: " + count);
}
}
Spliterator là low-level interface, thường không cần dùng trực tiếp. Stream API tự động sử dụng Spliterator internally. Chỉ cần hiểu concept để hiểu cách parallelStream() hoạt động.
Xem thêm: Module 11 - Functional Programming và Streams để tìm hiểu chi tiết về Stream API.
OCP Exam Traps - Bẫy Thường Gặp
Trap 1: iterator.remove() chỉ call được một lần sau mỗi next()
import java.util.*;
public class IteratorRemoveTrap {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Iterator<String> iter = list.iterator();
iter.next(); // A
iter.remove(); // OK - remove "A"
// ❌ Call remove() again WITHOUT calling next() first
try {
iter.remove(); // IllegalStateException!
} catch (IllegalStateException e) {
System.out.println("Trap 1: Cannot call remove() twice without next()");
}
// ✅ ĐÚNG: Must call next() before each remove()
iter.next(); // B
iter.remove(); // OK - remove "B"
iter.next(); // C
iter.remove(); // OK - remove "C"
System.out.println("Final list: " + list); // [D]
}
}
// Output:
// Trap 1: Cannot call remove() twice without next()
// Final list: [D]
Iterator<String> iter = list.iterator();
iter.remove(); // IllegalStateException - chưa gọi next()!
iter.next();
iter.remove(); // OK
iter.remove(); // IllegalStateException - chỉ được gọi 1 lần sau mỗi next()!
Trap 2: Comparator.reversed() trên chained comparator đảo ngược TOÀN BỘ
import java.util.*;
class Employee {
String name;
String dept;
int salary;
public Employee(String name, String dept, int salary) {
this.name = name; this.dept = dept; this.salary = salary;
}
@Override
public String toString() {
return name + " (" + dept + ", $" + salary + ")";
}
}
public class ComparatorReversedTrap {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", "IT", 80000),
new Employee("Bob", "HR", 70000),
new Employee("Charlie", "IT", 90000),
new Employee("David", "HR", 75000)
);
// ❌ NHẦM: Tưởng chỉ reverse salary
// THỰC TẾ: Reverse CẢ dept VÀ salary!
employees.sort(
Comparator.comparing(Employee::getDept)
.thenComparingInt(Employee::getSalary)
.reversed()
);
System.out.println("=== Reversed entire chain (WRONG if want dept ASC) ===");
employees.forEach(System.out::println);
// Output: Dept DESC, Salary DESC
// Charlie (IT, $90000)
// Alice (IT, $80000)
// David (HR, $75000)
// Bob (HR, $70000)
// ✅ ĐÚNG: Dept ASC, Salary DESC
employees.sort(
Comparator.comparing(Employee::getDept)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
);
System.out.println("\n=== Dept ASC, Salary DESC (CORRECT) ===");
employees.forEach(System.out::println);
// Output: Dept ASC, Salary DESC
// David (HR, $75000)
// Bob (HR, $70000)
// Charlie (IT, $90000)
// Alice (IT, $80000)
}
}
Câu hỏi: Output của đoạn code sau?
list.sort(Comparator.comparing(Person::getAge)
.thenComparing(Person::getName)
.reversed());
A) Age ASC, Name DESC B) Age DESC, Name ASC C) Age DESC, Name DESC ✅ ĐÚNG D) Age ASC, Name ASC
Giải thích: reversed() đảo ngược TOÀN BỘ comparator chain!
Trap 3: TreeSet elements MUST implement Comparable OR provide Comparator
import java.util.*;
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
// ❌ KHÔNG implement Comparable!
}
public class TreeSetComparableTrap {
public static void main(String[] args) {
// ❌ TRAP: Person không implement Comparable và không cung cấp Comparator
try {
Set<Person> people = new TreeSet<>();
people.add(new Person("Alice", 25)); // ClassCastException!
} catch (ClassCastException e) {
System.out.println("Trap 3: TreeSet requires Comparable or Comparator");
System.out.println("Exception: " + e.getMessage());
}
// ✅ SOLUTION 1: Provide Comparator
Set<Person> people1 = new TreeSet<>(Comparator.comparingInt(p -> p.age));
people1.add(new Person("Alice", 25));
people1.add(new Person("Bob", 30));
System.out.println("With Comparator: " + people1);
// ✅ SOLUTION 2: Make Person implement Comparable
Set<ComparablePerson> people2 = new TreeSet<>();
people2.add(new ComparablePerson("Alice", 25));
people2.add(new ComparablePerson("Bob", 30));
System.out.println("With Comparable: " + people2);
}
}
class ComparablePerson implements Comparable<ComparablePerson> {
String name;
int age;
public ComparablePerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(ComparablePerson other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
// ❌ Person không implement Comparable
Set<Person> set = new TreeSet<>();
set.add(new Person("Alice")); // ClassCastException at runtime!
// ✅ Must provide Comparator hoặc implement Comparable
Set<Person> set = new TreeSet<>(Comparator.comparing(Person::getName));
Trap 4: Comparator.comparing(null) → NullPointerException
import java.util.*;
public class ComparatorNullTrap {
public static void main(String[] args) {
List<String> list = Arrays.asList("Alice", "Bob", "Charlie");
// ❌ TRAP: Pass null to comparing()
try {
list.sort(Comparator.comparing(null)); // NullPointerException!
} catch (NullPointerException e) {
System.out.println("Trap 4: Comparator.comparing(null) throws NPE");
}
// ✅ ĐÚNG: Pass valid key extractor function
list.sort(Comparator.comparing(s -> s.length())); // OK
System.out.println("Sorted by length: " + list);
// ❌ TRAP: Null elements in list without nullsFirst/nullsLast
List<String> listWithNull = Arrays.asList("Alice", null, "Bob");
try {
listWithNull.sort(Comparator.naturalOrder()); // NullPointerException when comparing null!
} catch (NullPointerException e) {
System.out.println("Trap: Null element without nullsFirst/nullsLast");
}
// ✅ ĐÚNG: Use nullsFirst or nullsLast
listWithNull = Arrays.asList("Alice", null, "Bob");
listWithNull.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("With nullsFirst: " + listWithNull); // [null, Alice, Bob]
}
}
Mermaid Diagram - Iterator Internal State
Giải thích diagram:
- cursor: Index của phần tử tiếp theo sẽ return bởi
next() - lastRet: Index của phần tử vừa return bởi
next()gần nhất (-1 nếu chưa gọinext()hoặc đã gọiremove()) - expectedModCount: Snapshot của
modCounttại thời điểm tạo Iterator - Green path: Safe operations qua
Iterator.remove()→expectedModCountđược update - Red path: Unsafe operations qua
list.remove()→modCountthay đổi nhưngexpectedModCountkhông update → Exception!
Ví dụ thực hành
Ví dụ 1: Sort với multiple criteria
class Employee {
String name;
String department;
int salary;
int yearsOfService;
// Constructor, toString, getters...
}
public class MultiCriteriaSort {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", "IT", 80000, 5),
new Employee("Bob", "HR", 70000, 3),
new Employee("Charlie", "IT", 80000, 7),
new Employee("David", "IT", 90000, 5),
new Employee("Eve", "HR", 70000, 2)
);
// Sort by: Department → Salary (desc) → Years of Service (desc)
employees.sort(
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparingInt(Employee::getYearsOfService).reversed()
);
System.out.println("Sorted employees:");
employees.forEach(System.out::println);
}
}
Ví dụ 2: Custom Comparator cho TreeSet
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class TreeSetComparator {
public static void main(String[] args) {
// TreeSet với custom Comparator
Set<Person> peopleByAge = new TreeSet<>(
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName)
);
peopleByAge.add(new Person("Alice", 30));
peopleByAge.add(new Person("Bob", 25));
peopleByAge.add(new Person("Charlie", 30));
peopleByAge.add(new Person("Alice", 25)); // Same age as Bob
System.out.println("Sorted by age, then name:");
peopleByAge.forEach(System.out::println);
// Output:
// Alice(25)
// Bob(25)
// Alice(30)
// Charlie(30)
}
}
Ví dụ 3: Priority Queue với Comparator
class Task {
String name;
int priority;
long deadline; // Timestamp
// Constructor, toString, getters...
}
public class TaskPriorityQueue {
public static void main(String[] args) {
// Priority: priority first, then deadline
Queue<Task> taskQueue = new PriorityQueue<>(
Comparator.comparingInt(Task::getPriority)
.thenComparingLong(Task::getDeadline)
);
taskQueue.offer(new Task("Write report", 3, System.currentTimeMillis() + 86400000));
taskQueue.offer(new Task("Fix bug", 1, System.currentTimeMillis() + 3600000));
taskQueue.offer(new Task("Code review", 2, System.currentTimeMillis() + 7200000));
taskQueue.offer(new Task("Deploy", 1, System.currentTimeMillis() + 1800000));
System.out.println("Tasks by priority:");
while (!taskQueue.isEmpty()) {
System.out.println(taskQueue.poll());
}
}
}
Bài tập thực hành
Bài 1: Remove Duplicates with Iterator
Viết method void removeDuplicates(List<Integer> list) xóa các phần tử trùng lặp liên tiếp. Dùng Iterator.
Ví dụ: [1, 1, 2, 2, 2, 3, 4, 4] → [1, 2, 3, 4]
Bài 2: Multi-level Sorting
Tạo class Book với title, author, year, rating. Sort books by:
- Rating (descending)
- Year (descending)
- Title (ascending)
Bài 3: Top K Frequent Elements
Viết method List<Integer> topKFrequent(int[] nums, int k) tìm k phần tử xuất hiện nhiều nhất. Dùng HashMap và PriorityQueue với custom Comparator.
Bài 4: Custom Iterable
Tạo class FibonacciIterable implements Iterable<Integer> để iterate qua n số Fibonacci đầu tiên.
Tổng kết
Trong bài này, bạn đã học:
- Iterator: Duyệt collection, safe removal với
iter.remove() - ConcurrentModificationException: Xảy ra khi modify collection trong khi iterate, cách tránh
- Iterable: Interface cho phép dùng for-each loop
- Comparable: Natural ordering, implement trong class (
compareTo()) - Comparator: Custom ordering, separate object (
compare()) - Comparator methods (Java 8+):
comparing(),thenComparing(),reversed(),nullsFirst(),nullsLast() - Collections.sort() vs List.sort(): Prefer
List.sort()trong Java 8+
Quy tắc vàng:
- Xóa phần tử trong khi iterate → Dùng
Iterator.remove()hoặcremoveIf() - Natural ordering → Implement
Comparable - Multiple orderings → Dùng
Comparator - Java 8+ → Dùng
Comparator.comparing()với lambda/method reference