Chuyển tới nội dung chính

Iterator, Comparable và Comparator

Mục tiêu bài học

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ới compare()
  • Á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:

  1. Iterator tạo → snapshot expectedModCount = modCount hiện tại
  2. Mỗi lần next() → check modCount == expectedModCount
  3. Nếu list.add/remove (không qua Iterator) → modCount++ nhưng expectedModCount không đổi
  4. 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/clear
  • expectedModCount: Local snapshot trong mỗi Iterator, lưu giá trị modCount tại thời điểm tạo Iterator
  • checkForComodification(): So sánh modCount == 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()add() methods. Khác với list.add() thông thường, ListIterator.set()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ại
  • ListIterator.set(): An toàn, replace phần tử hiện tại
  • ListIterator.add(): An toàn, insert phần tử mới trước vị trí hiện tại
  • list.add(), list.remove(): KHÔNG AN TOÀN khi đang iterate → ConcurrentModificationException
Lưu ý

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ápCodeNote
Iterator.remove()iter.remove()Chỉ xóa được, không add được
Collect then removelist.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 loopfor (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);
}
compareTo() Return Values
// ✅ ĐÚ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);
}
}
🔥 Bẫy OCP

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()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ểmComparableComparator
Packagejava.langjava.util
Methodint compareTo(T o)int compare(T o1, T o2)
Modify classPhải modify class gốcKhông cần modify class gốc
Number of sortsChỉ 1 natural orderNhiều custom orders
Use caseDefault/natural orderingCustom/multiple orderings
SortingCollections.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);
Khi nào dùng gì?
  • 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+)
Khuyến nghị

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ĩaVí dụ
ORDEREDElements có thứ tự cố địnhList, LinkedHashSet
SORTEDElements đã được sắp xếpTreeSet, TreeMap
SIZEDBiết trước size chính xácArrayList, HashSet
SUBSIZEDSplit ra cũng biết sizeArrayList
DISTINCTKhông có duplicateSet implementations
IMMUTABLEKhông thể modifyList.of(), Set.of()
NONNULLKhông có null elementsList.of(), Set.of()
CONCURRENTThread-safe concurrent modificationConcurrentHashMap

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);
}
}
📖 Theo Oracle Docs

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]
🔥 Bẫy OCP
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)
}
}
🔥 Bẫy OCP

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 + ")";
}
}
🔥 Bẫy OCP
// ❌ 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ọi next() hoặc đã gọi remove())
  • expectedModCount: Snapshot của modCount tạ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()modCount thay đổi nhưng expectedModCount khô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:

  1. Rating (descending)
  2. Year (descending)
  3. 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:

  1. Xóa phần tử trong khi iterate → Dùng Iterator.remove() hoặc removeIf()
  2. Natural ordering → Implement Comparable
  3. Multiple orderings → Dùng Comparator
  4. Java 8+ → Dùng Comparator.comparing() với lambda/method reference

Đọc thêm