Por que não estou recebendo uma java.util.ConcurrentModificationException neste exemplo?

176

Nota: Estou ciente do Iterator#remove()método.

No exemplo de código a seguir, não entendo por que o método List.removein mainé lançado ConcurrentModificationException, mas não no removemétodo

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);
            }
        }
    }
}
Bhesh Gurung
fonte
3
A única maneira segura de remover um elemento de uma lista enquanto itera sobre essa lista é usar Iterator#remove(). Por que você está fazendo assim?
Matt Bola
@ MattBall: Eu estava apenas tentando ver qual poderia ser o motivo. Porque, é o mesmo "aprimorado para loop" nos dois métodos, mas um lança o ConcurrentModificationExceptione o outro não.
Bhesh Gurung
Existe uma diferença no elemento que você remove. No método, você remove o 'elemento do meio'. No principal você remove o último. Se você trocar os números, receberá a exceção no seu método. Ainda não sei por que isso acontece.
Ben van Gompel 18/11
Eu tive um problema semelhante, quando meu loop iterou também uma posição que não existia depois que eu removi um item no loop. Eu simplesmente consertei isso adicionando um return;no loop.
precisa saber é
no java8 Android, a remoção de outro elemento que não o último chamaria o ConcurrentModificationException. portanto, para o seu caso, a função remove receberia uma exceção oposta à observada anteriormente.
gonglong

Respostas:

262

Eis o porquê: Como se diz no Javadoc:

Os iteradores retornados pelos métodos iterator e listIterator dessa classe são rápidos: se a lista for modificada estruturalmente a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos métodos de remoção ou adição do próprio iterador, o iterador lançará uma ConcurrentModificationException.

Essa verificação é feita no next()método do iterador (como você pode ver no stacktrace). Mas chegaremos ao next()método somente se for hasNext()entregue true, que é o chamado pelo para cada um para verificar se o limite é atingido. No seu método remove, ao hasNext()verificar se ele precisa retornar outro elemento, verá que ele retornou dois elementos e, agora que um elemento foi removido, a lista contém apenas dois elementos. Então, tudo é pêssego e terminamos a iteração. A verificação de modificações simultâneas não ocorre, pois isso é feito no next()método que nunca é chamado.

Em seguida, chegamos ao segundo loop. Após removermos o segundo número, o método hasNext verificará novamente se é possível retornar mais valores. Ele já retornou dois valores, mas a lista agora contém apenas um. Mas o código aqui é:

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

1! = 2, continuamos com o next()método, que agora percebe que alguém está mexendo com a lista e aciona a exceção.

Espero que isso esclareça sua pergunta.

Resumo

List.remove()não será lançado ConcurrentModificationExceptionquando remover o segundo último elemento da lista.

insistente
fonte
5
@ pushy: Apenas respostas que parecem responder ao que a pergunta está realmente perguntando, e a explicação é boa. Estou aceitando esta resposta e também +1. Obrigado.
Bhesh Gurung
42

Uma maneira de lidar com isso é remover algo de uma cópia de um Collection(não da própria coleção), se aplicável. Clonea coleção original para fazer uma cópia via a Constructor.

Essa exceção pode ser lançada por métodos que detectaram a modificação simultânea de um objeto quando essa modificação não é permitida.

Para o seu caso específico, primeiro, acho que não finalé um caminho a percorrer, considerando que você pretende modificar a declaração anterior da lista

private static final List<Integer> integerList;

Considere também modificar uma cópia em vez da lista original.

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

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}
James Raitsev
fonte
14

O método de encaminhamento / iterador não funciona ao remover itens. Você pode remover o elemento sem erros, mas receberá um erro de tempo de execução ao tentar acessar os itens removidos. Você não pode usar o iterador porque, como mostra insistentemente, ele causará uma ConcurrentModificationException; portanto, use um loop for regular, mas retroceda nele.

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

Uma solução:

Atravesse a matriz na ordem inversa se você deseja remover um elemento da lista. Simplesmente, ao retroceder na lista, você evita visitar um item que foi removido, o que remove a exceção.

//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);
    }
}
RightHandedMonkey
fonte
ideia brilhante !
dobrivoje
7

Esse trecho sempre lançará uma ConcurrentModificationException.

A regra é "Você não pode modificar (adicionar ou remover elementos da lista) enquanto itera sobre ela usando um Iterator (o que acontece quando você usa um loop para cada um)".

JavaDocs:

Os iteradores retornados pelos métodos iterator e listIterator dessa classe são rápidos: se a lista for modificada estruturalmente a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos métodos de remoção ou adição do próprio iterador, o iterador lançará uma ConcurrentModificationException.

Portanto, se você deseja modificar a lista (ou qualquer coleção em geral), use o iterador, porque ele está ciente das modificações e, portanto, elas serão tratadas adequadamente.

Espero que isto ajude.

Bhushan
fonte
3
O OP afirma claramente que um dos loops NÃO gera uma exceção e o interrogador foi o motivo disso.
madth3
o que você quer dizer com 'interrogador'?
Bhushan
4

Eu tive o mesmo problema, mas no caso de adicionar um elemento en à lista iterada. Eu fiz assim

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

Agora tudo corre bem porque você não cria nenhum iterador sobre sua lista, mas itera-o "manualmente". E a condição i < integerList.size()nunca o enganará, porque quando você remove / adiciona algo ao tamanho da Lista do decremento / incremento da Lista.

Espero que ajude, para mim isso foi solução.

Gondil
fonte
Isso não é verdade ! Evidência: execute este snippet para ver o resultado: public static void main (String ... args) {List <> listOfBooks = new ArrayList <> (); listOfBooks.add ("Código concluído"); listOfBooks.add ("Código 22"); listOfBooks.add ("22 eficaz"); listOfBooks.add ("Netbeans 33"); System.err.println ("Antes de excluir:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Após excluir:" + listOfBooks); }
dobrivoje
1

Se você usar coleções de copiar na gravação, ele funcionará; no entanto, quando você usa list.iterator (), o Iterator retornado sempre fará referência à coleção de elementos como era quando (como abaixo) list.iterator () foi chamado, mesmo que outro thread modifique a coleção. Quaisquer métodos de mutação chamados em um Iterator ou ListIterator baseado em cópia na gravação (como adicionar, definir ou remover) lançarão uma UnsupportedOperationException.

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);
            }
        }
    }
}
JohnnyO
fonte
0

Isso funciona bem no Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.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);
            }
        }
    }
}

~%

battosai
fonte
Desculpe o erro de digitação Esta multa 'corre' em Java 1.6
battosai
Hmm ... Pode ser que você tenha uma implementação diferente. Mas de acordo com a especificação, é suposto fazer isso, IMO. Veja a resposta de @ Pushy.
Bhesh Gurung
infelizmente, id não no java 1.8
dobrivoje
0

No meu caso, eu fiz assim:

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

Mudança Iterator for eachpara for loopresolver.

E a razão é:

Os iteradores retornados pelos métodos iterator e listIterator dessa classe são rápidos: se a lista for modificada estruturalmente a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos métodos de remoção ou adição do próprio iterador, o iterador lançará uma ConcurrentModificationException.

- Documentos Java referenciados.

Stephen
fonte
-1

Verifique o seu código man ....

No método principal, você está tentando remover o quarto elemento que não existe e, portanto, o erro. No método remove () você está tentando remover o terceiro elemento que existe e, portanto, nenhum erro.

Abhishek
fonte
Você está enganado: os números 2e 3não são índices para a lista, mas elementos. A lógica de remoção verifica equalsos elementos da lista, não o índice dos elementos. Além disso, se fosse índice relacionado, seria IndexOutOfBoundsException, não ConcurrentModificationException.
Malte Hartwig