El principio subyacente y el uso correcto del ciclo foreach

prefacio

Las excepciones de modificación concurrente son: ConcurrentModificationException.

El bucle foreach es en realidad un bucle for mejorado. En comparación con el bucle for, es más conciso y se puede utilizar para atravesar matrices o colecciones. Su principio subyacente es realizar la función del iterador, por lo que, en esencia, foreach puede atravesar cualquier implementación Un objeto de la interfaz iterable.

1. El principio de implementación subyacente de foreach

foreach es esencialmente nada más que un envoltorio de "azúcar sintáctico" proporcionado por el compilador. Cuando el compilador encuentre for(Type item : arrayOrList) { }el código, lo traducirá.

1. Al usar foreach para atravesar la matriz, se realizará la siguiente traducción:

código fuente:

imagen

Código traducido:

imagen

Se puede encontrar que el código traducido es en realidad la forma en que generalmente usamos el ciclo for para recorrer la matriz, a saber:

for(int i=0;i<array.length;i++) {
    
    
    Type item = array[i];
    System.out.println(item);
}

2. Si es para atravesar el tipo de colección, se requiere que el tipo de colección atravesado implemente java.lang.Iterablela interfaz y iterator()devuelva un iterador Iterator en el método.

código fuente:

imagen

Código traducido:

imagen

Se puede encontrar que cuando se usa foreach para recorrer la colección, se traducirá a un iterador para realizar el recorrido de la colección. Ahora mismo:

Iterator iterator = strings.iterator();
while(iterator.hasNext()){
    
    
 String string = (String)iterator.next();
 System.out.println(string);
}

Dos, restricciones de uso foreach

  1. Foreach no puede modificar el valor de los elementos de la colección durante el proceso de recorrer la colección. Sin embargo, si está recorriendo una matriz, no está sujeto a esta limitación.
  2. Foreach no puede agregar o eliminar elementos de la colección durante el proceso transversal; de lo contrario, ConcurrentModificationExceptionse generará una excepción. Incluso si esta excepción no se lanza en casos especiales individuales, se debe a una coincidencia (se explica a continuación).
  3. Durante el proceso de recorrido, solo un elemento en la colección o matriz es visible al mismo tiempo, es decir, solo el "elemento actualmente atravesado" es visible, mientras que el elemento anterior o posterior es invisible.
  4. Solo puede atravesar hacia adelante de adelante hacia atrás, no hacia atrás.
Ejemplos de mal uso

Con respecto al punto 2, intente agregar o eliminar elementos durante el recorrido foreach:

ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
final String toRemove = "2";
final String toAdd = "1000";
for (String item : list) {
    
    
    //item = "100"; //这句执行无效,仅仅改变迭代器中item的指向,并不会真正改变list中的元素
    if (toRemove.equals(item)) {
    
    
        list.remove(item); //仅当toRemove为"3"时,没有报异常。这是删除倒数第二个元素情况下的“巧合”。
        //list.add(toAdd); // 报ConcurrentModificationException
    }
}

Análisis de las razones de la excepción de modificación concurrente arrojada en la situación anterior: 1. Hay una variable miembro dentro de ArrayList modCount, que se utiliza para registrar el número de veces que han cambiado los elementos de la lista.

2. Al iter=list.iterator()devolver un nuevo objeto iterador, iter utilizará expectedModCountvariables miembro para registrar el valor de modCount en ese momento. Durante el recorrido de todo el ciclo, tanto el método iter.next()como iter.remove()el método verificarán si el valor de modCount del ArrayList original es expectedModCountconsistente con el valor registrado en iter, y lo descartarán si es inconsistente ConcurrentModificationException. iter.next()Por lo tanto, la excepción se lanza en el método de la siguiente ronda de bucle después de agregar o restar elementos de colección .

3. Código fuente relacionado

public boolean hasNext() {
    
    
    return cursor != size;
}
public E next() {
    
    
 // 此处抛出异常
    checkForComodification();
    // 省略其他代码...
}
final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

4. Cuando toRemove es "3", no se informa ninguna excepción. ¿Por qué?

Porque, en este caso, una vez eliminada, el tamaño de la ArrayList original se reducirá en 1, y pasará la siguiente ronda iter.hasNext()(hasNext solo devuelve si la posición del iterador dentro del iterador ha alcanzado el tamaño del contenedor iterado, y no lanzar una excepción) Si no hay más elementos, devolverá falso directamente y iter.next()no se llamará al método. Por supuesto, no habrá excepciones lanzadas de este método.

usar correctamente

Entonces, la pregunta es, ¿qué debo hacer si necesito eliminar o agregar elementos en el proceso de recorrer la colección?

1. Ciclo for ordinario

for(int i=0;i<list.size();i++) {
    
    
    String item = list.get(i);
    if ("3".equals(item)) {
    
    
        list.remove(i);//为了效率,这里最好不要用list.remove(item)
    }
}

2. El iterador atraviesa el método del iterador y elimina mediante el método de eliminación del iterador.

Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
    
    
    String item = iter.next();
    if ("4".equals(item)) {
    
    
        iter.remove();
    }
}

Resumir

En el escenario de la subLista, preste mucha atención a la adición o eliminación de elementos en la colección principal, lo que provocará ConcurrentModificationExceptionanomalías en el recorrido, la adición y la eliminación de la sublista. ——Especificación de desarrollo Java de Alibaba (edición Songshan)

Supongo que te gusta

Origin blog.csdn.net/qq_43842093/article/details/131606031
Recomendado
Clasificación