Como evitar java.util.ConcurrentModificationException ao iterar e remover elementos de uma ArrayList

Eu tenho uma ArrayList que eu quero repetir. Enquanto iterar sobre isso, tenho que remover elementos ao mesmo tempo. Obviamente isso lança um java.util.ConcurrentModificationException .

Qual é a melhor prática para lidar com esse problema? Devo clonar a lista primeiro?

Eu removo os elementos não no loop em si, mas em outra parte do código.

Meu código é assim:

 public class Test() { private ArrayList abc = new ArrayList(); public void doStuff() { for (A a : abc) a.doSomething(); } public void removeA(A a) { abc.remove(a); } } 

a.doSomething pode chamar Test.removeA() ;

   

Duas opções:

  • Crie uma lista de valores que você deseja remover, adicionando a essa lista dentro do loop e, em seguida, chame originalList.removeAll(valuesToRemove) no final
  • Use o método remove() no próprio iterador. Observe que isso significa que você não pode usar o loop for aprimorado.

Como um exemplo da segunda opção, removendo todas as cadeias com um comprimento maior que 5 de uma lista:

 List list = new ArrayList(); ... for (Iterator iterator = list.iterator(); iterator.hasNext(); ) { String value = iterator.next(); if (value.length() > 5) { iterator.remove(); } } 

Dos JavaDocs da ArrayList

Os iteradores retornados pelos methods iterator e listIterator dessa class são fail-fast: se a lista for estruturalmente modificada a qualquer momento após a criação do iterador, de qualquer forma, exceto pelos methods remove ou add do iterador, o iterador lançará um ConcurrentModificationException.

Você deve apenas repetir o array da maneira tradicional

Toda vez que você remover um elemento da lista, os elementos posteriores serão empurrados para frente. Contanto que você não altere elementos além do iterativo, o código a seguir deve funcionar.

 public class Test(){ private ArrayList abc = new ArrayList(); public void doStuff(){ for(int i = (abc.size() - 1); i >= 0; i--) abc.get(i).doSomething(); } public void removeA(A a){ abc.remove(a); } } 

Uma opção é modificar o método removeA para isso –

 public void removeA(A a,Iterator iterator) { iterator.remove(a); } 

Mas isso significaria que seu doSomething() deveria ser capaz de passar o iterator para o método remove . Não é uma boa ideia.

Você pode fazer isso em uma abordagem em duas etapas: No primeiro loop, quando você percorrer a lista, em vez de remover os elementos selecionados, marque- os como para serem excluídos . Para isso, você pode simplesmente copiar esses elementos (cópia superficial) em outra List .

Então, uma vez que sua iteração é feita, simplesmente faça um removeAll da primeira lista todos os elementos na segunda lista.

Faça o loop da maneira normal, o java.util.ConcurrentModificationException é um erro relacionado aos elementos que são acessados.

Então tente:

 for(int i = 0; i < list.size(); i++){ lista.get(i).action(); } 

Você está tentando remover o valor da lista no “loop for” avançado, o que não é possível, mesmo se você aplicar algum truque (o que você fez no seu código). Melhor maneira é codificar o nível do iterador como outro recomendado aqui.

Eu me pergunto como as pessoas não sugeriram a abordagem tradicional de loop.

 for( int i = 0; i < lStringList.size(); i++ ) { String lValue = lStringList.get( i ); if(lValue.equals("_Not_Required")) { lStringList.remove(lValue); i--; } } 

Isso funciona também.

Aqui está um exemplo onde eu uso uma lista diferente para adicionar os objects para remoção, depois eu uso stream.foreach para remover elementos da lista original:

 private ObservableList customersTableViewItems = FXCollections.observableArrayList(); ... private void removeOutdatedRowsElementsFromCustomerView() { ObjectProperty currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime()); long diff; long diffSeconds; List objectsToRemove = new ArrayList<>(); for(CustomerTableEntry item: customersTableViewItems) { diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime(); diffSeconds = diff / 1000 % 60; if(diffSeconds > 10) { // Element has been idle for too long, meaning no communication, hence remove it System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName()); objectsToRemove.add(item); } } objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o)); } 

No Java 8, você pode usar a interface de coleta e fazer isso chamando o método removeIf:

 yourList.removeIf((A a) -> a.value == 2); 

Mais informações podem ser encontradas aqui

Faça algo simples assim:

 for (Object object: (ArrayList) list.clone()) { list.remove(object); } 

Em vez de usar For each loop, use normal for loop. Por exemplo, o código abaixo remove todo o elemento na lista de matriz sem fornecer java.util.ConcurrentModificationException. Você pode modificar a condição no loop de acordo com o seu caso de uso.

  for(int i=0;i 

“Devo clonar a lista primeiro?”

Essa será a solução mais fácil, remova do clone e copie o clone de volta após a remoção.

Um exemplo do meu jogo de rummikub:

 SuppressWarnings("unchecked") public void removeStones() { ArrayList clone = (ArrayList) stones.clone(); // remove the stones moved to the table for (Stone stone : stones) { if (stone.isOnTable()) { clone.remove(stone); } } stones = (ArrayList) clone.clone(); sortStones(); } 

Uma solução alternativa do Java 8 usando stream:

  theList = theList.stream() .filter(element -> !shouldBeRemoved(element)) .collect(Collectors.toList()); 

No Java 7, você pode usar o Guava:

  theList = FluentIterable.from(theList) .filter(new Predicate() { @Override public boolean apply(String element) { return !shouldBeRemoved(element); } }) .toImmutableList(); 

Observe que o exemplo do Guava resulta em uma lista imutável que pode ou não ser o que você deseja.

Enquanto iterar a lista, se você quiser remover o elemento é possível. Vamos ver abaixo meus exemplos,

 ArrayList names = new ArrayList(); names.add("abc"); names.add("def"); names.add("ghi"); names.add("xyz"); 

Eu tenho os nomes acima da lista Array. E eu quero remover o nome “def” da lista acima,

 for(String name : names){ if(name.equals("def")){ names.remove("def"); } } 

O código acima lança a exceção ConcurrentModificationException porque você está modificando a lista durante a iteração.

Então, para remover o nome “def” do Arraylist fazendo isso,

 Iterator itr = names.iterator(); while(itr.hasNext()){ String name = itr.next(); if(name.equals("def")){ itr.remove(); } } 

O código acima, através do iterador, podemos remover o nome “def” do Arraylist e tentar imprimir o array, você veria a saída abaixo.

Saída: [abc, ghi, xyz]

Se seu objective é remover todos os elementos da lista, você pode iterar sobre cada item e, em seguida, chamar:

 list.clear()