56 votes

Exception de modification simultanée : ajout à une ArrayList

Le problème se produit à

Element element = it.next();

Et ce code qui contient cette ligne, se trouve à l'intérieur d'un fichier OnTouchEvent

for (Iterator<Element> it = mElements.iterator(); it.hasNext();){
    Element element = it.next();

    if(touchX > element.mX  && touchX < element.mX + element.mBitmap.getWidth() && touchY > element.mY   
            && touchY < element.mY + element.mBitmap.getHeight()) {  

        //irrelevant stuff..

        if(element.cFlag){
            mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            element.cFlag = false;

        }           
    }
}

Tout ceci est à l'intérieur synchronized(mElements) , donde mElements est un ArrayList<Element>

Quand je touche un Element il peut activer cFlag ce qui créera une autre Element avec des propriétés différentes, qui tombera de l'écran et se détruira en moins d'une seconde. C'est ma façon de créer des effets de particules. Nous pouvons appeler cela "particule" crack comme le paramètre String dans le constructeur.

Tout cela fonctionne bien jusqu'à ce que j'ajoute un autre principal Element . Maintenant, j'ai deux Elements sur l'écran en même temps, et si je touche la plus récente Element cela fonctionne bien, et lance les particules.

Cependant, si je touche et active cFlag sur les anciens Element puis il me donne l'exception.

 07-28 15:36:59.815: ERROR/AndroidRuntime(4026): FATAL EXCEPTION: main
07-28 15:36:59.815: ERROR/AndroidRuntime(4026): java.util.ConcurrentModificationException
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:573)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.Juggle2.Panel.onTouchEvent(Panel.java:823)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.view.View.dispatchTouchEvent(View.java:3766)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:863)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:863)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1767)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1119)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.app.Activity.dispatchTouchEvent(Activity.java:2086)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1751)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1785)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.os.Handler.dispatchMessage(Handler.java:99)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.os.Looper.loop(Looper.java:123)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at android.app.ActivityThread.main(ActivityThread.java:4627)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at java.lang.reflect.Method.invokeNative(Native Method)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at java.lang.reflect.Method.invoke(Method.java:521)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:651)
07-28 15:36:59.815: ERROR/AndroidRuntime(4026):     at dalvik.system.NativeStart.main(Native Method)

Comment puis-je faire en sorte que cela fonctionne ?

83voto

user802421 Points 3315

ConcurrentModificationException se produit lorsque vous modifiez la liste (en ajoutant ou en supprimant des éléments) tout en parcourant une liste à l'aide de la commande Iterator .

Essayez

List<Element> thingsToBeAdd = new ArrayList<Element>();
for(Iterator<Element> it = mElements.iterator(); it.hasNext();) {
    Element element = it.next();
    if(...) {  
        //irrelevant stuff..
        if(element.cFlag){
            // mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            thingsToBeAdd.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
            element.cFlag = false;
        }           
    }
}
mElements.addAll(thingsToBeAdd );

Vous devriez également envisager d'améliorer chaque boucle comme Jon l'a suggéré.

63voto

konmik Points 121

J'utilise normalement quelque chose comme ça :

for (Element element : new ArrayList<Element>(mElements)) {
    ...
}

rapide, propre et sans bug

une autre option consiste à utiliser CopyOnWriteArrayList

21voto

Jon Skeet Points 692016

Vous n'êtes pas autorisé à ajouter une entrée à une collection pendant que vous l'itérez.

Une option consiste à créer une nouvelle List<Element> pour les nouvelles entrées pendant que vous itérez sur mElements et ensuite ajouter tous les nouveaux à mElement après ( mElements.addAll(newElements) ). Bien sûr, cela signifie que vous n'aurez pas exécuté le corps de la boucle pour ces nouveaux éléments - est-ce un problème ?

Dans le même temps, je vous recommande de mettre à jour votre code afin d'utiliser la fonction amélioré pour la boucle :

for (Element element : mElements) {
    ...
}

14voto

Ixx Points 7104

Une boucle for indexée devrait également fonctionner.

for (int i = 0; i < collection.size(); i++)

2voto

gnat Points 5565

L'ajout de la liste dans ce cas conduit à la FMC, aucune quantité de synchronized vous permettra d'éviter cela. Au lieu de cela, envisagez d'ajouter l'utilisation de l'itérateur...

        for(ListIterator<Element> it = mElements.listIterator(); it.hasNext();){
            Element element = it.next();

            if(touchX > element.mX  && touchX < element.mX + element.mBitmap.getWidth() && touchY > element.mY   
                    && touchY < element.mY + element.mBitmap.getHeight()) {  

                //irrelevant stuff..

                if(element.cFlag){
                    // mElements.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
                    it.add(new Element("crack",getResources(), (int)touchX,(int)touchY));
                    element.cFlag = false;

                }           
            }
        }

Je pense aussi que c'est un peu glissant à déclarer comme...

...Le problème se produit à Element element = it.next();

pour des raisons de précision, notez que ce qui précède n'est pas garanti.

Documentation de l'API souligne que cette ...le comportement ne peut être garanti car il est, en général, impossible d'apporter des garanties solides en présence de modifications concurrentes non synchronisées. Les opérations rapides lancent ConcurrentModificationException sur un objet de type principe du meilleur effort ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X