imprimir - linkedlist en java ejemplo




Iterando a través de una colección, evitando ConcurrentModificationException al modificar(eliminar) en bucle (15)

Todos sabemos que no puedes hacer esto:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... esto parece funcionar a veces, pero no siempre. Aquí hay un código específico:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

Esto, por supuesto, resulta en:

Exception in thread "main" java.util.ConcurrentModificationException

... aunque varios hilos no lo están haciendo ... De todos modos.

¿Cuál es la mejor solución a este problema? ¿Cómo puedo eliminar un elemento de la colección en un bucle sin lanzar esta excepción?

También estoy usando una Collection arbitraria aquí, no necesariamente una ArrayList , por lo que no puedes confiar en get .


La mejor manera (recomendada) es el uso del paquete java.util.Concurrent. Al utilizar este paquete, puede evitar fácilmente esta excepción. refiera el código modificado

public static void main(String[] args) {
        Collection<Integer> l = new CopyOnWriteArrayList<Integer>();

        for (int i=0; i < 10; ++i) {
            l.add(new Integer(4));
            l.add(new Integer(5));
            l.add(new Integer(6));
        }

        for (Integer i : l) {
            if (i.intValue() == 5) {
                l.remove(i);
            }
        }

        System.out.println(l);
    }

Con Eclipse Collections (anteriormente GS Collections ), el método removeIf definido en MutableCollection funcionará:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Con la sintaxis de Java 8 Lambda esto se puede escribir de la siguiente manera:

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

La llamada a Predicates.cast() es necesaria aquí porque se agregó un método removeIf predeterminado en la interfaz java.util.Collection en Java 8.

Nota: Soy un comendador de Eclipse Collections .


Con un tradicional para bucle.

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

Ejemplo de modificación de la colección segura de hilos:

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}

En tales casos, un truco común es (¿era?) Retroceder:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Dicho esto, estoy más que contento de tener mejores formas en Java 8, por ejemplo, removeIf o filter on streams.


Esto funciona:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

Asumí que, dado que un bucle foreach es azúcar sintáctica para la iteración, usar un iterador no ayudaría ... pero le ofrece esta funcionalidad .remove() .


La gente está afirmando que no se puede eliminar de una colección que está iterando por un bucle foreach. Solo quería señalar que es técnicamente incorrecto y describir exactamente (sé que la pregunta del OP es tan avanzada que se debe obviar saber esto) el código detrás de esa suposición:

    for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
        if (obj.isTouched()) {
            untouchedSet.remove(obj);
            touchedSt.add(obj);
            break;  // this is key to avoiding returning to the foreach
        }
    }

No es que no se pueda eliminar de la Colletion iterada, sino que no se puede continuar con la iteración una vez que lo haga. De ahí la break en el código anterior.

Le pido disculpas si esta respuesta es un caso de uso un tanto especializado y más adecuado para el thread original del que llegué aquí, este se marca como un duplicado (a pesar de que este hilo aparece más matizado) de este y se bloquea.


La misma respuesta que Claudius con un bucle for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

Puede que esta no sea la mejor manera, pero para la mayoría de los casos pequeños, esto debería ser aceptable:

"crea una segunda matriz vacía y agrega solo las que quieras conservar"

No recuerdo de dónde leí esto desde ... por razones de justicia haré este wiki con la esperanza de que alguien lo encuentre o simplemente para no obtener una reputación que no merezco.


Puede usar el iterador directamente como lo mencionó, o bien mantener una segunda colección y agregar cada elemento que desee eliminar a la nueva colección, luego eliminar todo al final. Esto le permite seguir usando la seguridad de tipos del bucle for-each a costa de un mayor uso de memoria y tiempo de CPU (no debería ser un gran problema a menos que tenga listas realmente grandes o una computadora muy vieja)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

Tengo una sugerencia para el problema anterior. No hay necesidad de lista secundaria o tiempo extra. Por favor encuentre un ejemplo que haga lo mismo pero de una manera diferente.

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

Esto evitaría la excepción de concurrencia.


Un ListIterator permite agregar o eliminar elementos de la lista. Supongamos que tiene una lista de objetos de Car :

List<Car> cars = ArrayList<>();
// add cars here...

for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
{
   if (<some-condition>)
   { 
      carIterator().remove()
   }
   else if (<some-other-condition>)
   { 
      carIterator().add(aNewCar);
   }
}


Iterator.remove() es seguro, puedes usarlo así:

List<String> list = new ArrayList<>();

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

Tenga en cuenta que Iterator.remove() es la única forma segura de modificar una colección durante la iteración; el comportamiento no se especifica si la colección subyacente se modifica de otra manera mientras la iteración está en curso.

Fuente: docs.oracle.com/javase/tutorial/collections/interfaces/…

Y de manera similar, si tiene un ListIterator y desea agregar elementos, puede usar ListIterator#add , por la misma razón que puede usar Iterator#remove - está diseñado para permitirlo.

En su caso, intentó eliminar de una lista, pero se aplica la misma restricción si trata de put en un Map mientras itera su contenido.


for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

El problema es después de eliminar el elemento de la lista si omite la llamada interna iterator.next (). ¡aún funciona! Aunque no me propongo escribir código como este, me ayuda a entender el concepto que hay detrás :-)

¡Aclamaciones!





collections