1. Bối cảnh về cách thức ra đời của iterator

Bạn đã quen thuộc với HashSet. Nếu bạn đã thực sự tìm hiểu nó, ngoài việc chỉ đọc một bài học, thì bạn nên đặt câu hỏi này:

Làm cách nào để hiển thị danh sách tất cả các phần tử HashSet trên màn hình? Rốt cuộc, giao diện không có get()set()phương pháp!

HashSetkhông đơn độc trong giới hạn này. Ngoài HashSet, còn có nhiều tập hợp khác không cho phép truy xuất các phần tử theo chỉ mục, bởi vì các phần tử không có thứ tự xác định.

Trong những năm qua, các lập trình viên đã phát minh ra rất nhiều cấu trúc dữ liệu phức tạp, chẳng hạn như biểu đồ và cây. Hoặc danh sách của danh sách.

Nhiều vùng chứa thay đổi thứ tự các phần tử của chúng khi các phần tử mới được thêm vào hoặc các phần tử hiện có bị xóa. Ví dụ: một danh sách lưu trữ các phần tử theo một thứ tự cụ thể và khi một phần tử mới được thêm vào, phần tử đó hầu như luôn được chèn vào giữa danh sách.

Và chúng ta cũng gặp tình huống có một thùng chứa lưu trữ các phần tử nhưng không theo bất kỳ thứ tự cố định nào.

Bây giờ, giả sử chúng ta muốn sao chép tất cả các phần tử từ một bộ sưu tập như vậy vào một mảng hoặc danh sách. Chúng ta cần phải có được tất cả các yếu tố. Chúng tôi không quan tâm đến thứ tự mà chúng tôi lặp lại các phần tử — điều quan trọng là không lặp lại các phần tử giống nhau nhiều lần. làm sao chúng ta làm việc đó bây giờ?


2. Iterator cho một bộ sưu tập

Trình lặp được đề xuất như một giải pháp cho vấn đề trên.

Trình lặp là một đối tượng đặc biệt được liên kết với một bộ sưu tập, giúp duyệt qua tất cả các phần tử của bộ sưu tập mà không lặp lại bất kỳ phần tử nào.

Bạn có thể sử dụng đoạn mã sau để nhận một trình vòng lặp cho bất kỳ bộ sưu tập nào:

Iterator<Type> it = name.iterator();

Đâu namelà tên của biến bộ sưu tập, Typelà loại phần tử của bộ sưu tập, iterator()là một trong các phương thức của bộ sưu tập và itlà tên của biến lặp.

Một đối tượng iterator có 3 phương thức:

Phương pháp Sự miêu tả
Type next()
Trả về phần tử tiếp theo trong bộ sưu tập
boolean hasNext()
Kiểm tra xem có phần tử nào chưa được duyệt không
void remove()
Xóa phần tử hiện tại của bộ sưu tập

Các phương thức này hơi giống với các phương thức của lớp nextInt)Scanner hasNextInt().

Phương next()thức trả về phần tử tiếp theo của bộ sưu tập mà từ đó chúng ta có trình vòng lặp.

Phương hasNext()thức kiểm tra xem bộ sưu tập có các phần tử bổ sung mà trình vòng lặp chưa trả về hay không.

Đây là cách hiển thị tất cả các phần tử của a HashSet:

Mã số ghi chú
HashSet<String> set = new HashSet<String>();

set.add("Hallo");
set.add("Hello");
set.add("Hola");
set.add("Bonjour");
set.add("Ciao");
set.add("Namaste");

Iterator<String> it = set.iterator();
while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}
Tạo một HashSetđối tượng lưu trữ Stringcác phần tử.


Chúng tôi thêm lời chào bằng các ngôn ngữ khác nhau vào setbiến.




Nhận một đối tượng lặp cho settập hợp.
Miễn là vẫn còn phần tử

Lấy phần tử tiếp theo
Hiển thị phần tử trên màn hình


3. For-eachvòng lặp

Nhược điểm chính của trình vòng lặp là mã của bạn trở nên cồng kềnh hơn so với sử dụng forvòng lặp.

Để so sánh, hãy hiển thị một danh sách bằng cách sử dụng một forvòng lặp và cũng sử dụng một trình vòng lặp:

Trình lặp cho vòng lặp
ArrayList<String> list = new ArrayList<String>();

Iterator<String> it = list.iterator();
while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();

for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);
   System.out.println(str);
}

Vâng, tốt hơn hết là duyệt qua các phần tử của ArrayListmột vòng lặp — mọi thứ trở nên ngắn hơn.

Nhưng những người tạo ra Java lại quyết định đổ một ít đường vào chúng tôi. May mắn cho chúng tôi, đó là đường cú pháp .

Họ đã cung cấp cho Java một loại vòng lặp mới và gọi nó là for-eachvòng lặp. Đây là cách nó trông nói chung:

for(Type name:collection)

Đâu collectionlà tên của biến tập hợp, Typelà kiểu của các phần tử trong tập hợp và namelà tên của một biến lấy giá trị tiếp theo từ tập hợp ở mỗi lần lặp của vòng lặp.

Loại vòng lặp này lặp qua tất cả các phần tử của bộ sưu tập bằng cách sử dụng trình vòng lặp ẩn. Đây là cách nó thực sự hoạt động:

Đối với mỗi vòng lặp Những gì trình biên dịch nhìn thấy: Vòng lặp với một trình vòng lặp
ArrayList<String> list = new ArrayList<String>();

for (String str: list)
{
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();
Iterator<String> it = list.iterator();

while (it.hasNext())
{
   String str = it.next();
   System.out.println(str);
}

Khi trình biên dịch gặp một for-eachvòng lặp trong mã của bạn, nó chỉ cần thay thế nó bằng mã ở bên phải: nó thêm một lệnh gọi để nhận một trình vòng lặp cùng với bất kỳ lệnh gọi phương thức bị thiếu nào khác.

Các lập trình viên yêu thích for-eachvòng lặp và hầu như luôn sử dụng nó khi họ cần lặp lại tất cả các phần tử của một tập hợp.

Ngay cả việc lặp lại ArrayListdanh sách bằng for-eachvòng lặp cũng có vẻ ngắn hơn:

Đối với mỗi vòng lặp cho vòng lặp
ArrayList<String> list = new ArrayList<String>();

for (String str: list)
{
   System.out.println(str);
}
ArrayList<String> list = new ArrayList<String>();

for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);
   System.out.println(str);
}


4. Loại bỏ một phần tử trong for-eachvòng lặp

Vòng for-eachlặp có một nhược điểm: nó không thể loại bỏ các phần tử một cách chính xác. Nếu bạn viết mã như thế này, bạn sẽ gặp lỗi.

Mã số Ghi chú
ArrayList<String> list = new ArrayList<String>();

list.add("Hallo");
list.add("Hello");
list.add("Hola");
list.add("Bonjour");
list.add("Ciao");
list.add("Namaste");

for (String str: list)
{
   if (str.equals("Hello"))
      list.remove(str);
}












Thao tác xóa sẽ phát sinh lỗi!

Đây là một mã rất hay và dễ hiểu, nhưng nó sẽ không hoạt động.

Quan trọng!

Bạn không thể thay đổi một bộ sưu tập trong khi duyệt qua nó bằng một trình vòng lặp.

Có ba cách để vượt qua giới hạn này.

1. Sử dụng một loại vòng lặp khác

When traversing an ArrayList collection, bạn có thể sử dụng vòng lặp thông thường với ibiến đếm.

Mã số
for (int i = 0; i < list.size(); i++)
{
   String str = list.get(i);

   if (str.equals("Hello"))
   {
      list.remove(str);
      i--; // We need to decrease i, because the remove operation shifted the elements
   }
}

Tuy nhiên, tùy chọn này không phù hợp với HashSetHashMapbộ sưu tập

2. Sử dụng trình vòng lặp rõ ràng

Bạn có thể sử dụng trình vòng lặp một cách rõ ràng và gọi remove()phương thức của nó.

Phiên bản hoạt động Phiên bản không hoạt động
Iterator<String> it = set.iterator();
while (it.hasNext())
{
   String str = it.next();
   if (str.equals("Hello"))
       it.remove();
}

for (String str: list) { if (str.equals("Hello")) list.remove(str); }

Lưu ý rằng chúng ta gọi remove()phương thức trên đối tượng iterator! Trình lặp biết rằng mục đã bị xóa và có thể xử lý tình huống một cách chính xác.

3. Sử dụng một bản sao của bộ sưu tập

Bạn cũng có thể tạo một bản sao của bộ sưu tập, sau đó sử dụng bản sao đó trong một for-eachvòng lặp và xóa các phần tử khỏi bộ sưu tập gốc.

Mã số Ghi chú
ArrayList<String> listCopy = new ArrayList(list);

for (String str: listCopy)
{
   if (str.equals("Hello"))
      list.remove(str);
}
Tạo một bản sao của bộ sưu tập cực kỳ dễ dàng



Vòng lặp sử dụng trình vòng lặp cho bản sao của bộ sưu tập.
Các yếu tố được loại bỏ khỏi listbộ sưu tập.

Bộ sưu tập được sao chép khá nhanh vì bản thân các phần tử không bị trùng lặp. Thay vào đó, bộ sưu tập mới lưu trữ các tham chiếu đến các phần tử đã tồn tại trong bộ sưu tập cũ.