Bạn có thể làm những gì bạn muốn nếu bạn sử dụng một đối tượng trình lặp để xem qua các phần tử trong tập hợp của mình. Bạn có thể xóa chúng khi đang di chuyển, không sao cả. Tuy nhiên, việc xóa chúng khi đang ở trong vòng lặp for ("tiêu chuẩn", của từng loại) sẽ khiến bạn gặp rắc rối:
Set<Integer> set = new TreeSet<Integer>();
set.add(1);
set.add(2);
set.add(3);
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
Integer setElement = iterator.next();
if(setElement==2) {
iterator.remove();
}
}
for(Integer setElement:set) {
if(setElement==2) {
set.remove(setElement);
}
}
Theo nhận xét của @ mrgloom, đây là chi tiết hơn về lý do tại sao cách "tồi tệ" được mô tả ở trên, cũng ... tệ:
Nếu không đi vào quá nhiều chi tiết về cách Java thực hiện điều này, ở mức độ cao, chúng ta có thể nói rằng cách "xấu" là không tốt vì nó được quy định rõ ràng như vậy trong các tài liệu Java:
https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html
quy định, trong số những người khác, rằng (tôi nhấn mạnh):
" Ví dụ: thường không được phép cho một luồng sửa đổi Bộ sưu tập trong khi một luồng khác đang lặp lại nó. Nói chung, kết quả của việc lặp lại không được xác định trong những trường hợp này. triển khai do JRE cung cấp) có thể chọn bỏ ngoại lệ này nếu hành vi này được phát hiện "(...)
" Lưu ý rằng ngoại lệ này không phải lúc nào cũng chỉ ra rằng một đối tượng đã được sửa đổi đồng thời bởi một luồng khác. Nếu một luồng duy nhất đưa ra một chuỗi các lệnh gọi phương thức vi phạm hợp đồng của một đối tượng, thì đối tượng có thể ném ra ngoại lệ này. Ví dụ: nếu một luồng sửa đổi trực tiếp một bộ sưu tập trong khi nó đang lặp lại bộ sưu tập bằng một trình vòng lặp không nhanh, trình vòng lặp sẽ ném ra ngoại lệ này. "
Để đi sâu hơn vào chi tiết: một đối tượng có thể được sử dụng trong vòng lặp forEach cần triển khai giao diện "java.lang.Iterable" (javadoc tại đây ). Điều này tạo ra một Iterator (thông qua phương thức "Iterator" được tìm thấy trong giao diện này), được khởi tạo theo yêu cầu và sẽ chứa nội bộ một tham chiếu đến đối tượng Iterable mà từ đó nó được tạo ra. Tuy nhiên, khi một đối tượng Iterable được sử dụng trong vòng lặp forEach, phiên bản của trình lặp này sẽ bị ẩn đối với người dùng (bạn không thể tự mình truy cập nó theo bất kỳ cách nào).
Điều này, cùng với thực tế là một Iterator khá trạng thái, tức là để thực hiện phép thuật của nó và có các phản hồi nhất quán cho các phương thức "tiếp theo" và "hasNext" của nó, nó cần đối tượng hỗ trợ không bị thay đổi bởi một thứ khác ngoài chính trình lặp trong khi nó đang lặp lại, hãy làm cho nó để nó sẽ ném ra một ngoại lệ ngay khi nó phát hiện có điều gì đó đã thay đổi trong đối tượng sao lưu khi nó đang lặp lại nó.
Java gọi đây là sự lặp lại "không nhanh": tức là có một số hành động, thường là những hành động sửa đổi một thể hiện Iterable (trong khi một Iterator đang lặp lại nó). Phần "fail" của khái niệm "fail-fast" đề cập đến khả năng của một Iterator phát hiện khi nào các hành động "thất bại" như vậy xảy ra. Phần "nhanh" của "không nhanh" (và theo ý kiến của tôi nên được gọi là "nỗ lực nhanh nhất"), sẽ kết thúc quá trình lặp lại qua ConcurrentModificationException ngay khi nó có thể phát hiện ra rằng hành động "không thành công" có xảy ra.