Interface Iterator<E>
Iterator<E> là một giao diện trong Java được sử dụng để lặp qua các phần tử của một bộ sưu tập (collection). Nó cung cấp một cách tiêu chuẩn và linh hoạt hơn để duyệt qua các phần tử so với cách cũ là sử dụng Enumeration.
Sự khác biệt giữa Iterator và Enumeration
↳ Xóa phần tử trong quá trình lặp: Iterator cho phép bạn xóa các phần tử khỏi bộ sưu tập mà bạn đang lặp qua, trong khi Enumeration không cho phép điều này.
↳ Tên phương thức: Iterator có các tên phương thức rõ ràng hơn và dễ hiểu hơn so với Enumeration. Ví dụ, Iterator sử dụng hasNext() và next(), trong khi Enumeration sử dụng hasMoreElements() và nextElement().
Các phương thức của Interface Iterator<E>
Trong Java, Iterator (bộ lặp) là một đối tượng đóng vai trò như một con trỏ di động, giúp bạn duyệt qua từng phần tử của một tập hợp (như List, Set, v.v.) mà không cần biết cấu trúc bên trong của tập hợp đó. Nó cung cấp một cách tiêu chuẩn để truy cập và đôi khi xóa các phần tử.
Dưới đây là ba phương thức chính của Iterator:
1. Phương thức boolean hasNext()
↳ Mô tả: Phương thức này giống như việc bạn hỏi "Còn phần tử nào nữa để tôi xem không?". Nó sẽ trả về true nếu vẫn còn các phần tử chưa được duyệt qua trong tập hợp. Ngược lại, nếu bạn đã duyệt đến cuối tập hợp và không còn phần tử nào nữa, nó sẽ trả về false.
↳ Giá trị trả về: boolean (true nếu còn phần tử, false nếu hết).
↳ Ví dụ: Bạn dùng nó trong vòng lặp while để kiểm tra xem có nên gọi next() hay không.
2. Phương thức E next()
↳ Mô tả: Nếu hasNext() trả về true, thì next() sẽ "cung cấp" cho bạn phần tử tiếp theo trong chuỗi. Sau khi trả về phần tử, Iterator sẽ tự động di chuyển con trỏ của nó đến vị trí của phần tử kế tiếp.
↳ Giá trị trả về: E (kiểu dữ liệu của phần tử tiếp theo).
↳ Ngoại lệ: Ném ra NoSuchElementException nếu bạn cố gắng gọi next() khi không còn phần tử nào nữa (tức là khi hasNext() đã trả về false). Vì vậy, luôn luôn gọi hasNext() trước khi gọi next() để tránh lỗi này.
Ví dụ
Iterator<String> it = myCollection.iterator();
while (it.hasNext()) {
String element = it.next(); // Lấy phần tử tiếp theo
System.out.println(element);
}
3. Phương thức void remove()
↳ Mô tả: Phương thức này cho phép bạn xóa phần tử cuối cùng mà next() vừa trả về khỏi tập hợp gốc.
Lưu ý quan trọng:
↳ Đây là một tùy chọn (optional operation). Không phải tất cả các Iterator đều hỗ trợ việc xóa.
↳ Bạn chỉ có thể gọi remove() một lần duy nhất sau mỗi lần gọi next().
↳ Nếu bạn thay đổi tập hợp gốc bằng cách nào đó khác ngoài việc dùng remove() của Iterator này, hành vi của Iterator sẽ không được đảm bảo (có thể gây lỗi ConcurrentModificationException).
Ngoại lệ:
↳ UnsupportedOperationException: Nếu Iterator không hỗ trợ thao tác xóa.
↳ IllegalStateException: Nếu next() chưa được gọi lần nào, hoặc remove() đã được gọi rồi sau lần gọi next() cuối cùng.
Ví dụ
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
Iterator<String> nameIterator = names.iterator();
while (nameIterator.hasNext()) {
String name = nameIterator.next();
if (name.equals("Bob")) {
nameIterator.remove(); // Xóa "Bob" khỏi danh sách
}
}
System.out.println(names); // Output: [Alice, Charlie]
Fail-Fast Iterator
Fail-Fast Iterator là một cơ chế trong các bộ sưu tập (collections) của Java, đảm bảo rằng nếu một cấu trúc dữ liệu bị thay đổi sau khi một iterator được tạo, iterator sẽ ngay lập tức ném ra một ngoại lệ ConcurrentModificationException. Điều này giúp phát hiện lỗi đồng thời bảo vệ cấu trúc dữ liệu khỏi các hành vi bất thường hoặc không mong muốn trong quá trình duyệt.
Cách hoạt động của Fail-Fast Iterator
↳ Phát hiện thay đổi: Khi bạn tạo một iterator để lặp qua một tập hợp (ví dụ: ArrayList, HashSet, LinkedHashSet, v.v.), iterator sẽ theo dõi bất kỳ sự thay đổi nào trong cấu trúc của tập hợp đó (ví dụ: thêm, xóa, hoặc thay đổi phần tử).
↳ Ném ngoại lệ: Nếu tập hợp bị thay đổi sau khi iterator được tạo (ngoại trừ việc sử dụng các phương thức của chính iterator như remove()), iterator sẽ ngay lập tức ném ra một ngoại lệ ConcurrentModificationException. Điều này giúp tránh các hành vi không thể đoán trước, không xác định khi lặp qua một tập hợp mà cấu trúc của nó đã bị thay đổi.
↳ Chỉ là nỗ lực tốt nhất (Best-Effort): Cần lưu ý rằng Fail-Fast Iterator không thể đảm bảo hoàn toàn việc phát hiện mọi thay đổi. Nó hoạt động trên cơ chế "best-effort", nghĩa là nó sẽ cố gắng phát hiện và xử lý các thay đổi, nhưng không có sự đảm bảo cứng nhắc. Vì vậy, bạn không nên phụ thuộc vào việc ngoại lệ này xảy ra để đảm bảo tính đúng đắn của chương trình.
Ví dụ: Example.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// Tạo một iterator
Iterator<String> iterator = list.iterator();
// Trong khi lặp, nếu có thay đổi trong list, sẽ ném ConcurrentModificationException
while (iterator.hasNext()) {
System.out.println(iterator.next());
list.add("D"); // Thêm phần tử mới vào list trong khi đang lặp
}
}
}
Kết quả của chương trình là:
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049) at Example.main(Example.java:17)
Fail-Fast Iterator giúp bảo vệ chương trình khỏi các lỗi không mong muốn do việc thay đổi cấu trúc của tập hợp trong quá trình lặp. Nó cung cấp một cách phát hiện và phản ứng nhanh chóng bằng cách ném ra ngoại lệ ConcurrentModificationException.
Hy vọng phần giải thích này giúp bạn hiểu rõ hơn về giao diện Iterator và các phương thức của nó trong Java!