2014-07-03 14 views
10

Mam poniższy kod i oczekuję, że rzuci ConcurrentModificationException, ale działa poprawnie. Dlaczego to się dzieje?Nie rzuca wyjątku ConcurrentModificationException

public void fun(){ 
    List <Integer>lis = new ArrayList<Integer>(); 
    lis.add(1); 
    lis.add(2); 

    for(Integer st:lis){ 
     lis.remove(1); 
     System.out.println(lis.size()); 
    } 
} 

public static void main(String[] args) { 
    test t = new test(); 
    t.fun(); 
} 
+0

Po co ten błąd? [ArrayList.remove()] (http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#remove (int)) nie wyrzuca tego błędu, tylko indeks z miedza. – SyntaxTerror

+1

psssst! .... nigdy nie widziałem mojej odpowiedzi na pytanie :) –

+0

możliwy duplikat [usunięcia obiektu z tablicy listy za pomocą wyjątków foreach tylko wtedy, gdy rozmiar listy przekracza 2] (http://stackoverflow.com/questions/20040015/remove-object-from-arraylist-using-foreach-exception-occours-only-when-the-lis) – vandale

Odpowiedz

10

Metoda remove(int) na List usuwa element w określonej pozycji. Przed rozpoczęciem pętli, lista wygląda następująco:

[1, 2] 

Wtedy zaczynasz iterator na liście:

[1, 2] 
^ 

Twój for pętla następnie usuwa elementu w pozycji 1, który jest numer 2:

[1] 
^ 

iterator, na kolejnej domniemanej hasNext() rozmowy zwraca false, a pętla zostaje zakończona.

Otrzymasz ConcurrentModificationException, jeśli dodasz więcej elementów do listy. Wtedy zostanie wyrzucony niejawny next().

Jako notatkę, z Javadoc dla ArrayList od FKŻ:

Uwaga że nie szybki zachowanie iteratora nie może być zagwarantowane, jak to jest, ogólnie rzecz biorąc, niemożliwe, aby jakiekolwiek twarde gwarancje obecność niezsynchronizowanej równoczesnej modyfikacji. Fail-szybkie iteratory rzucać ConcurrentModificationException na zasadzie najlepszego wysiłku. Dlatego błędem byłoby napisanie programu, który zależałby od tego wyjątku pod względem poprawności: szybkie działanie iteratorów powinno być używane tylko do wykrywania błędów.

Jest to prawdopodobnie błąd w implementacji iteratora Oracle ArrayList; hasNext() robi nie czek na modyfikacji:

public boolean hasNext() { 
    return cursor != size; 
} 
0

W tej pętli:

for(Integer st:lis){ 
     lis.remove(1); 
     System.out.println(lis.size()); 
    } 

Jesteś tylko ciągle usunięcie elementu z indeksem 1 z matrycy, nawet nie dbając, co jest w st. Więc ta pętla iz każdej iteracji spróbuje usunąć element z modyfikacją indeks 1. Concurent będzie pochodzić z pętli nNiniejszego:

for(Integer st:lis){ 
    lis.remove(st); 
    System.out.println(lis.size()); 
} 
+2

To jest właściwie niepoprawne, ponieważ usuwa element z indeksu 1. Usunąłby on wartość 1, jeśli Java jest autoboxing int 1 do Integer 1. –

+1

Masz rację i byłem głupi. Nie mogę uwierzyć, że ta odpowiedź została dwukrotnie wznowiona. Spróbuję to naprawić. – Niemand

1

jeśli masz listę 3 jak:

lis.add(1); 
lis.add(2); 
lis.add(3); 

będziesz uzyskać ConcurrentModificationException w twoim przypadku. PS: Próbowałem tego!

1

Ponieważ nie usuwają 1, wyjmowania element z 1. (remove(int) vs remove(Object))

Iterator sprawdza tylko do modyfikacji na wywołanie next() nie hasNext() i pętla wyjdzie po wywołaniu hasNext(), ponieważ usunięto 2, lista jest tylko jedna długa i tym samym kończy działanie.

+1

W rzeczywistości, jeśli usuwasz w indeksie 0, to również nie rzuca wyjątku. Proszę przetestuj swoje odpowiedzi przed ich opublikowaniem. – Ordous

+1

@Ozdobna jest na tej samej zasadzie, że pętla zostanie zakończona, zanim sprawdzi, czy lista została zmieniona. – vandale

+0

Ta sama zasada, ale pierwsze zdanie jest zupełnie nieistotne, a "Ponieważ" rzuca każdego, kto czyta to powód. – Ordous

0

masz tylko 2 pozycje w liście. Tak więc pętla działa tylko raz, ponieważ usuwasz wpis.

ConcurrentModificationException zostanie zgłoszony, jeśli lista zostanie zmodyfikowana i ponownie spróbujesz wykonać na niej operację. Ale zanim wykonamy jakąkolwiek operację, jesteśmy poza pętlą, a więc nie ma wyjątków. Spróbuj dodać kolejną pozycję do listy i uruchom program, który rzuci wyjątek.

3

Nie rzuca wyjątku ConcurrentModificationException, ponieważ, jak powiedział vandale, iterator sprawdza tylko comodification na next(). Oto część instancji Iterator zwrócony przez ArrayList:

public boolean hasNext() { 
     return cursor != size; 
    } 

    @SuppressWarnings("unchecked") 
    public E next() { 
     checkForComodification(); 
     int i = cursor; 
     if (i >= size) 
      throw new NoSuchElementException(); 
     Object[] elementData = ArrayList.this.elementData; 
     if (i >= elementData.length) 
      throw new ConcurrentModificationException(); 
     cursor = i + 1; 
     return (E) elementData[lastRet = i]; 
    } 

hasNext() po prostu sprawdza, czy kursor wskazuje na ostatni indeks listy. Nie sprawdza, czy lista została zmodyfikowana. Z tego powodu nie masz wyjątku ConcurrentModificationException, po prostu przestaje on iterować.

1

Istotą sprawy jest, jak stwierdzono zarówno ArrayList i ConcurrentModificationException:

Uwaga że nie szybki zachowanie iteratora nie może być zagwarantowane, jak to jest, ogólnie rzecz biorąc, niemożliwe, aby każdy ciężko gwarancje w obecności niezsynchronizowanej równoczesnej modyfikacji. Fail-szybko iteratory rzucić ConcurrentModificationException na zasadzie najlepszego wysiłku.

Teraz przykładowy kod z Iterator zwrócony przez ArrayList:

public boolean hasNext() { 
     return cursor != size; 
    } 

    public E next() { 
     checkForComodification(); 
     <stuff> 
     return <things>; 
    } 

    <more methods> 

    final void checkForComodification() { 
     if (modCount != expectedModCount) 
      throw new ConcurrentModificationException(); 
    } 

Jak można wyraźnie zobaczyć, w przypadku ArrayList „best effort” jest sprawdzanie modyfikacji Dzwoniąc next() i nie podczas wywoływania getNext(). Twoja pętla kończy się bez wywoływania po raz drugi next(), a więc bez wyjątku. Jeśli masz 3 elementy na początek, lub dodaj element, to się nie powiedzie. Warto również zauważyć, że jeśli zmodyfikujesz listę tablic za pomocą refleksji bez aktualizacji zmiennej modCount (niegrzeczny ...), to wyjątek nie zostanie w ogóle rzucony. modCount również nie jest niestabilny, co po raz kolejny pokazuje, że jest to tylko najlepszy wysiłek i nie ma żadnych gwarancji, ponieważ iterator może i tak nie widzieć najnowszej wartości.