Możesz robić, co chcesz, jeśli użyjesz obiektu iteratora do przejrzenia elementów w swoim zestawie. Możesz je usunąć w drodze i wszystko w porządku. Jednak usunięcie ich w pętli for (albo „standardowej”, albo dla każdego rodzaju) spowoduje kłopoty:
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);
}
}
Zgodnie z komentarzem @ mrgloom, oto więcej szczegółów wyjaśniających, dlaczego „zły” sposób opisany powyżej jest… cóż… zły:
Bez wchodzenia w zbyt wiele szczegółów na temat tego, jak Java implementuje to, na wysokim poziomie, możemy powiedzieć, że „zły” sposób jest zły, ponieważ jest jasno określony jako taki w dokumentacji Java:
https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html
zastrzegam między innymi, że (wyróżnienie moje):
" Na przykład, nie jest na ogół dopuszczalne jeden wątek zmodyfikować Collection a drugi gwint jest kolejno po nim. W ogólności, wyniki iteracji jest zdefiniowana w tych warunkach. Niektóre implementacje iterator (włącznie z wszystkimi zbierania General Purpose implementacje udostępnione przez środowisko JRE) mogą zgłosić ten wyjątek, jeśli zostanie wykryte to zachowanie „(...)
" Należy pamiętać, że ten wyjątek nie zawsze wskazują, że obiekt został jednocześnie modyfikowana przez inny wątek. Jeśli pojedyncze kwestie gwintów sekwencja metody wywołania, które naruszają umowę obiektu, obiekt może rzucić ten wyjątek. Na przykład, jeśli Wątek modyfikuje kolekcję bezpośrednio podczas iteracji po kolekcji z iteratorem działającym w trybie fail-fast, iterator zgłosi ten wyjątek. "
Aby przejść do szczegółów: obiekt, który może być użyty w pętli forEach, musi zaimplementować interfejs „java.lang.Iterable” ( tutaj javadoc ). Tworzy to Iterator (za pomocą metody „Iterator” znajdującej się w tym interfejsie), który jest tworzony na żądanie i będzie zawierał wewnętrznie odniesienie do obiektu Iterowalnego, z którego został utworzony. Jednak gdy w pętli forEach jest używany obiekt iterowalny, instancja tego iteratora jest ukryta dla użytkownika (nie można uzyskać do niego w żaden sposób dostępu samodzielnie).
To, w połączeniu z faktem, że Iterator jest dość stanowy, tj. Aby wykonać swoją magię i mieć spójne odpowiedzi na swoje metody "next" i "hasNext", potrzebuje, aby obiekt bazowy nie został zmieniony przez coś innego niż sam iterator podczas iteracji sprawia, że zgłasza wyjątek, gdy tylko wykryje, że coś się zmieniło w obiekcie bazowym podczas iteracji po nim.
Java nazywa tę iterację „odporną na awarie”: tj. Istnieją pewne akcje, zwykle takie, które modyfikują instancję iterowalną (podczas gdy Iterator ją iteruje). Część „niepowodzenia” pojęcia „szybkiego niepowodzenia” odnosi się do zdolności Iteratora do wykrywania, kiedy takie „niepowodzenie” ma miejsce. „Szybka” część „fail-fast” (a, który moim zdaniem powinien być nazywany „best-effort-fast”), zakończy iteracji poprzez ConcurrentModificationException tak szybko, jak to może wykryć , że „nie” działanie ma zdarzyć.