Excepción de modificación simultánea: agregar a una ArrayList

Resuelto asked hace 54 años • 11 respuestas

El problema ocurre en

Element element = it.next();

Y este código que contiene esa línea, está dentro de unOnTouchEvent

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;

        }           
    }
}

Todo esto está dentro synchronized(mElements), donde mElementshay unArrayList<Element>

Cuando toco un Element, puede que se active cFlag, lo que creará otro Elementcon propiedades diferentes, que se caerá de la pantalla y se destruirá en menos de un segundo. Es mi forma de crear efectos de partículas. Podemos llamar a esto "partícula" crack, como el parámetro String en el constructor.

Todo esto funciona bien hasta que agrego otro main Element. Ahora tengo dos Elementsen pantalla al mismo tiempo, y si toco el más nuevo Element, funciona bien y lanza las partículas.

Sin embargo, si toco y activo cFlagel anterior Element, me da la excepción.

 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)

¿Cómo puedo hacer que esto funcione?

 avatar Jan 01 '70 08:01
Aceptado

ConcurrentModificationException ocurre cuando modifica la lista (agregando o eliminando elementos) mientras recorre una lista con Iterator.

Intentar

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 );

También deberías considerar mejorar cada bucle como sugirió Jon.

user802421 avatar Jul 28 '2011 21:07 user802421

Normalmente uso algo como esto:

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

rápido, limpio y sin errores

otra opción es usar CopyOnWriteArrayList

konmik avatar Jan 20 '2015 09:01 konmik

No puedes agregar una entrada a una colección mientras estás iterando sobre ella.

Una opción es crear una nueva List<Element>para las nuevas entradas mientras estás iterando mElementsy luego agregar todas las nuevas mElementdespués ( mElements.addAll(newElements)). Por supuesto, eso significa que no habrás ejecutado el cuerpo del bucle para esos nuevos elementos. ¿Es eso un problema?

Al mismo tiempo, te recomiendo que actualices tu código para usar el bucle for mejorado :

for (Element element : mElements) {
    ...
}
Jon Skeet avatar Jul 28 '2011 21:07 Jon Skeet