Wywołanie remove W pętli foreach w Javie [duplikat]

To pytanie ma już odpowiedź tutaj:

W Javie, czy jest legalne wywoływanie remove na kolekcji podczas iteracji poprzez kolekcję za pomocą pętli foreach? Na przykład:

List<String> names = ....
for (String name : names) {
   // Do something
   names.remove(name).
}

Jako dodatek, czy usuwanie przedmiotów jest legalne to jeszcze nie zostało powtórzone? Na przykład,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
    // Do something
    while (names.remove(name));
}
Author: James McMahon, 2009-07-29

11 answers

Aby bezpiecznie usunąć kolekcję podczas iteracji, należy użyć iteratora.

Na przykład:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call i.remove()
   // Do something
   i.remove();
}

Z dokumentacji Javy :

Iteratory zwracane przez iteratora i listeratora tej klasy metody są Fail-fast: jeśli lista jest strukturalnie zmodyfikowana w dowolnym czasu po utworzeniu iteratora, w jakikolwiek sposób, z wyjątkiem własne metody usuwania lub dodawania iteratora, iterator wyrzuci ConcurrentModificationException. Tak więc, w obliczu równoczesnego modyfikacji, iterator nie działa szybko i czysto, a nie ryzykując arbitralne, niedeterministyczne zachowanie w nieokreślonym czasie w przyszłości.

Być może to, co jest niejasne dla wielu nowicjuszy, to fakt, że iteracja nad listą za pomocą konstruktów for / foreach pośrednio tworzy iterator, który jest koniecznie niedostępny. Informacje te można znaleźć tutaj

 792
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-02 11:11:03

Nie chcesz tego robić. Może powodować nieokreślone zachowanie w zależności od kolekcji. Chcesz użyć iteratora bezpośrednio. Chociaż dla każdego konstruktu jest cukrem składniowym i naprawdę używa iteratora, ukrywa go przed Twoim kodem, więc nie możesz uzyskać do niego dostępu Iterator.remove.

Zachowanie iteratora jest nieokreślony, jeżeli bazowy kolekcja jest modyfikowana podczas iteracja trwa w jakikolwiek sposób inne niż nazywając to metoda.

Zamiast tego napisz swój kod:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {

    String name = it.next();
    // Do something
    it.remove();
}

Zauważ, że kod wywołuje Iterator.remove, a nie List.remove.

Dodatek:

Nawet jeśli usuwasz element, który nie został jeszcze iterowany, nadal nie chcesz modyfikować kolekcji, a następnie używać Iterator. Może to zmodyfikować zbiór w sposób zaskakujący i wpływający na przyszłe operacje na Iterator.

 152
Author: Jared Oberhaus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 20:48:37

Konstrukcja Javy "enhanced for loop" miała nie narazić iteratora na kod, ale jedynym sposobem na bezpieczne usunięcie elementu jest dostęp do iteratora. Więc w tym przypadku musisz to zrobić po staremu:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
       String name = i.next();
       //Do Something
       i.remove();
 }

Jeśli w kodzie rzeczywistym pętla enhanced for jest naprawdę tego warta, możesz dodać elementy do kolekcji tymczasowej i wywołać removeAll na liście po pętli.

EDIT (re addendum): nie, Zmiana listy w jakikolwiek sposób poza iteratorem.metoda remove () while iteracja spowoduje problemy. Jedynym sposobem obejścia tego jest użycie CopyOnWriteArrayList, ale tak naprawdę jest to przeznaczone do problemów z współbieżnością.

Najtańszym (jeśli chodzi o linie kodu) sposobem na usunięcie duplikatów jest zrzut listy do LinkedHashSet (a następnie z powrotem do listy, jeśli zajdzie taka potrzeba). Pozwala to zachować kolejność wstawiania podczas usuwania duplikatów.

 55
Author: Yishai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-18 13:31:40
for (String name : new ArrayList<String>(names)) {
    // Do something
    names.remove(nameToRemove);
}

Sklonujesz listę names i powtarzasz przez klon podczas usuwania z oryginalnej listy. Trochę czystsza niż najlepsza odpowiedź.

 44
Author: ktamlyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-22 03:37:30

Nie wiedziałem o iteratorach, jednak oto, co robiłem do dziś, aby usunąć elementy z listy wewnątrz pętli:

List<String> names = .... 
for (i=names.size()-1;i>=0;i--) {    
    // Do something    
    names.remove(i);
} 

To zawsze działa i może być używane w innych językach lub strukturach nie obsługujących iteratorów.

 24
Author: Serafeim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-27 14:53:42

Tak możesz użyć pętli for-each, Aby to zrobić, musisz zachować oddzielną listę, aby trzymać usuwanie elementów, a następnie usunąć tę listę z listy nazw za pomocą metody removeAll(),

List<String> names = ....

// introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();

for (String name : names) {
   // Do something: perform conditional checks
   toRemove.add(name);
}    
names.removeAll(toRemove);

// now names list holds expected values
 20
Author: Chathuranga Withana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-11 13:00:54

Te, które mówią, że nie można bezpiecznie usunąć elementu z kolekcji poza iteratorem, nie są całkiem poprawne, można to zrobić bezpiecznie używając jednej z współbieżnych kolekcji, takich jak ConcurrentHashMap.

 3
Author: sanity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-28 23:25:51

Upewnij się, że to nie zapach kodu. Czy można odwrócić logikę i być "inkluzywnym", a nie "wyłącznym"?

List<String> names = ....
List<String> reducedNames = ....
for (String name : names) {
   // Do something
   if (conditionToIncludeMet)
       reducedNames.add(name);
}
return reducedNames;

Sytuacja, która doprowadziła mnie do tej strony dotyczyła starego kodu, który przechodził przez Listę używając indecies do usuwania elementów z listy. Chciałem go refaktorować, aby użyć stylu foreach.

Przepętał całą listę elementów, aby sprawdzić, do których z nich użytkownik miał uprawnienia dostępu, i usunął te, które nie miały uprawnień z lista.

List<Service> services = ...
for (int i=0; i<services.size(); i++) {
    if (!isServicePermitted(user, services.get(i)))
         services.remove(i);
}

Aby to odwrócić i nie używać Usuń:

List<Service> services = ...
List<Service> permittedServices = ...
for (Service service:services) {
    if (isServicePermitted(user, service))
         permittedServices.add(service);
}
return permittedServices;

Kiedy preferowane jest "Usuń"? Jednym z rozważań jest, jeśli Gien dużą listę lub drogie "dodać", w połączeniu z tylko kilka usunięte w porównaniu do wielkości listy. Może być bardziej wydajne, aby zrobić tylko kilka usunięć, a nie wiele dodań. Ale w moim przypadku sytuacja nie zasługiwała na taką optymalizację.

 3
Author: bmcdonald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-14 16:57:57
    Spróbuj tego 2. i zmień warunek na "zimowy" a będziesz się zastanawiał:
public static void main(String[] args) {
  Season.add("Frühling");
  Season.add("Sommer");
  Season.add("Herbst");
  Season.add("WINTER");
  for (String s : Season) {
   if(!s.equals("Sommer")) {
    System.out.println(s);
    continue;
   }
   Season.remove("Frühling");
  }
 }
 1
Author: Carsten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-30 15:42:18

Lepiej jest użyć iteratora, gdy chcemy usunąć element z listy

Ponieważ kod źródłowy remove To

if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
elementData[--size] = null;

Tak więc, jeśli usuniesz element z listy, lista zostanie zrestrukturyzowana, indeks innego elementu zostanie zmieniony, może to spowodować coś, co chcesz się stało.

 1
Author: song,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-09 08:51:44

Użyj

.remove() Of Interator or

Użyj

CopyOnWriteArrayList

 -5
Author: ThmHarsh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-10 05:41:46