Neden bu örnekte bir java.util.ConcurrentModificationException almıyorum?


176

Not: Iterator#remove()Yöntemin farkındayım .

Neden Aşağıdaki kod örneğinde, anlamıyorum List.removeiçinde mainyöntem atar ConcurrentModificationException, ama değil de removeyönteme.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
Bir liste üzerinde yineleme yaparken bir öğeyi listeden kaldırmanın tek güvenli yolu kullanmaktır Iterator#remove(). Neden böyle yapıyorsun?
Matt Ball

@MattBall: Sadece buradaki nedenin ne olabileceğini görmeye çalışıyordum. Çünkü, her iki yöntemde de aynı "döngü için geliştirilmiş", ancak biri atar ConcurrentModificationException, diğeri atmaz.
Bhesh Gurung

Kaldırdığınız öğede bir fark var.Yöntemde 'orta elemanı' kaldırın. Ana bölümde sonuncuyu kaldırırsınız. Sayıları değiştirirseniz, yönteminizde istisna elde edersiniz. Yine de bunun neden olduğundan emin değilim.
Ben van Gompel

Döngümde bir öğeyi kaldırdıktan sonra varolmayan bir konumu yinelediğimde benzer bir sorun yaşadım. Ben sadece return;döngü içine bir ekleyerek bu düzeltildi .
frank17

java8 Android'de, sonuncusu dışındaki öğeyi kaldırmak ConcurrentModificationException öğesini çağırır. yani sizin durumunuz için, kaldırma fonksiyonu daha önce gözlemlediğinizin tersi bir istisna alır.
gonglong

Yanıtlar:


262

Nedeni: Javadoc'ta söylediği gibi:

Bu sınıfın yineleyicisi ve listIterator yöntemleri tarafından döndürülen yineleyiciler hata hızındadır: liste yineleyici oluşturulduktan sonra herhangi bir zamanda yapısal olarak değiştirilirse, yineleyicinin kendi kaldırma veya ekleme yöntemleri dışında herhangi bir şekilde yineleyici bir ConcurrentModificationException özel durumu atar.

Bu kontrol next()yineleyicinin yönteminde yapılır (yığın izlemesinde görebileceğiniz gibi). Ancak, next()yalnızca hasNext()doğru teslim edilirse , yönteme ulaşacağız , bu da sınırın karşılanıp karşılanmadığını kontrol etmek için her biri için denir. Kaldırma yönteminizde, hasNext()başka bir öğeyi döndürmesi gerekip gerekmediğini denetlerken, öğenin iki öğe döndürdüğünü görür ve şimdi bir öğe kaldırıldıktan sonra liste yalnızca iki öğe içerir. Yani her şey şeftali ve yineleme ile işimiz bitti. next()Asla çağrılmayan yöntemde yapıldığı için, eşzamanlı değişikliklerin kontrolü gerçekleşmez .

Sonra ikinci döngüye geçiyoruz. İkinci sayıyı kaldırdıktan sonra hasNext yöntemi daha fazla değer döndürüp döndüremeyeceğini tekrar kontrol eder. Zaten iki değer döndürdü, ancak liste artık yalnızca bir değer içeriyor. Ancak buradaki kod:

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

1! = 2, bu yüzden next()birisinin listeyle uğraştığını ve istisnayı tetiklediğini fark eden yönteme devam ediyoruz .

Umarım sorunuzu çözer.

özet

List.remove()atmaz ConcurrentModificationExceptiono listeden ikinci son öğe kaldırır.


5
@pushy: Sadece sorunun gerçekte sorduğu soruya cevap gibi görünen ve açıklama iyi. Bu yanıtı ve + 1'i kabul ediyorum. Teşekkürler.
Bhesh Gurung

42

Uygulamanın bir yolu Collection, varsa (Koleksiyonun kendisi değil) bir kopyasından bir şey kaldırmaktır . Cloneorijinal koleksiyonu a Constructor.

Bu istisna, böyle bir modifikasyona izin verilmediğinde bir nesnenin eşzamanlı modifikasyonunu tespit eden yöntemler ile atılabilir.

Özel durumunuz için, ilk önce final, listeyi geçmiş beyanı değiştirmeyi düşündüğünüzü düşünmenin bir yolu olduğunu düşünmüyorum

private static final List<Integer> integerList;

Ayrıca orijinal liste yerine bir kopyasını değiştirmeyi düşünün.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

Öğeleri kaldırırken ileri / yineleyici yöntemi çalışmaz. Öğeyi hatasız olarak kaldırabilirsiniz, ancak kaldırılan öğelere erişmeye çalıştığınızda bir çalışma zamanı hatası alırsınız. Yineleyiciyi gösterdiği için bir ConcurrentModificationException özelliğine neden olacağından yineleyiciyi kullanamazsınız, bunun yerine bunun için normal bir döngü kullanın, ancak geriye doğru adım atın.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Bir çözüm:

Bir liste öğesini kaldıracaksanız diziyi ters sırayla izleyin. Listede geriye doğru gittiğinizde, kaldırılmış bir öğeyi ziyaret etmekten kaçınırsınız, bu da istisnayı kaldırır.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

parlak fikir !
dobrivoje

7

Bu snippet her zaman bir ConcurrentModificationException kurar.

Kural, "Bir Yineleyici kullanarak (her biri için bir döngü kullandığınızda gerçekleşir) yineleme yaparken listeden öğe ekleyemez (listeye öğe ekleyemez veya listeden öğe kaldıramazsınız)" şeklindedir.

JavaDocs:

Bu sınıfın yineleyicisi ve listIterator yöntemleri tarafından döndürülen yineleyiciler hata hızındadır: liste yineleyici oluşturulduktan sonra herhangi bir zamanda yapısal olarak değiştirilirse, yineleyicinin kendi kaldırma veya ekleme yöntemleri dışında herhangi bir şekilde yineleyici bir ConcurrentModificationException özel durumu atar.

Bu nedenle, listeyi (veya genel olarak herhangi bir koleksiyonu) değiştirmek istiyorsanız, yineleyiciyi kullanın, çünkü o zaman değişikliklerin farkındadır ve dolayısıyla bunlar düzgün bir şekilde ele alınacaktır.

Bu yardımcı olur umarım.


3
OP, döngülerden birinin bir istisna atmadığını açıkça belirtir ve bunun neden olduğu sorgulamadır.
madth3

'sorgulayıcı' ile ne demek istiyorsun?
Bhushan

4

Aynı sorunu yaşadım ama yinelenen listeye en element ekliyordu. Bu şekilde yaptım

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Şimdi her şey yolunda gidiyor çünkü listenizde yineleyici oluşturmuyorsanız, bunu "manuel" olarak yineliyorsunuz. Ve durumi < integerList.size() sizi asla kandıramaz, çünkü Liste büyüklüğünü / artışını Liste boyutuna kaldırdığınızda / eklediğinizde ..

Umarım yardımcı olur, benim için bu bir çözümdü.


Bu doğru değil ! Kanıt: sonucu görmek için bu snippet'i çalıştırın: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Kod Tamamlandı"); listOfBooks.add ("Kod 22"); listOfBooks.add ("22 Etkili"); listOfBooks.add ("Netbeans 33"); System.err.println ("Silmeden önce:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (dizin); }} System.err.println ("Sildikten sonra:" + listOfBooks); }
dobrivoje

1

Yazarken kopyala koleksiyonları kullanırsanız çalışır; ancak list.iterator () kullandığınızda, döndürülen Iterator öğesi, başka bir iş parçacığı koleksiyonu değiştirse bile, her zaman olduğu gibi (aşağıdaki gibi) list.iterator () çağrıldığı zaman öğe koleksiyonuna başvurur. Yazmaya kopya tabanlı bir Yineleme veya ListIterator'da (ekleme, ayarlama veya kaldırma gibi) çağrılan herhangi bir mutasyon yöntemi bir UnsupportedOperationException özel durumu oluşturur.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

Bu Java 1.6'da iyi çalışır

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat KaldırListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%


Yazım hatası için üzgünüm Bu Java 1.6 'iyi çalışır'
battosai

Hmm ... Farklı bir uygulamanız olabilir. Ancak spesifikasyona göre bunu yapması gerekiyor, IMO. @ Pushy'nin cevabına bakın.
Bhesh Gurung

ne yazık ki, id java 1.8 üzerinde değil
dobrivoje

0

Benim durumumda böyle yaptım:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

Yineleyiciyi Değiştir for each içine for loopçözmek için.

Sebep:

Bu sınıfın yineleyicisi ve listIterator yöntemleri tarafından döndürülen yineleyiciler hata hızındadır: liste yineleyici oluşturulduktan sonra herhangi bir zamanda yapısal olarak değiştirilirse, yineleyicinin kendi kaldırma veya ekleme yöntemleri dışında herhangi bir şekilde yineleyici bir ConcurrentModificationException özel durumu atar.

--Belirli Java Belgeleri.


-1

Kodunuzu kontrol edin ...

Ana yöntemde, orada olmayan 4. elemanı ve dolayısıyla hatayı kaldırmaya çalışıyorsunuz. Remove () yönteminde, var olan 3. öğeyi kaldırmaya çalışıyorsunuz ve dolayısıyla hata yok.


Yanılıyorsunuz: sayılar 2ve 3liste için endeksler değil, öğeler. Her iki kaldırma mantığı equalsda öğelerin dizinini değil liste öğelerini denetler . Dahası, eğer indeksle ilgili olsaydı IndexOutOfBoundsException, olmazdı ConcurrentModificationException.
Malte Hartwig
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.