Obtendo uma ConcurrentModificationException lançada ao remover um elemento de um java.util.List durante a iteração de lista?

@Test public void testListCur(){ List li=new ArrayList(); for(int i=0;i<10;i++){ li.add("str"+i); } for(String st:li){ if(st.equalsIgnoreCase("str3")) li.remove("str3"); } System.out.println(li); } 

Quando eu executo este código, vou lançar um ConcurrentModificationException.

Parece que quando eu removo o elemento especificado da lista, a lista não sabe que seu tamanho foi alterado.

Eu estou querendo saber se isso é um problema comum com collections e remoção de elementos?

Eu acredito que este é o propósito por trás do método Iterator.remove () , para poder remover um elemento da coleção enquanto iterar.

Por exemplo:

 Iterator iter = li.iterator(); while(iter.hasNext()){ if(iter.next().equalsIgnoreCase("str3")) iter.remove(); } 

Observe que essa exceção nem sempre indica que um object foi modificado simultaneamente por um thread diferente. Se um único encadeamento emite uma sequência de invocações de método que viola o contrato de um object, o object pode lançar essa exceção. Por exemplo, se um encadeamento modifica uma coleção diretamente enquanto iterar sobre a coleção com um iterador fail-fast, o iterador exibirá essa exceção

Extraído de http://download.oracle.com/javase/1.4.2/docs/api/java/util/ConcurrentModificationException.html

A maneira do Java 8 para removê-lo da lista sem o Iterator é:

 li.removeIf() 

ou seja

 List li = new ArrayList(); // ... li = li.removeIf(st -> !st.equalsIgnoreCase("str3")); 

Sim, as pessoas correm para ele – o problema é que você não pode modificar a lista enquanto iterar sobre ela. Eu usei 2 alternativas no passado:

  1. Você pode acompanhar os índices dos itens que deseja remover e removê-los depois de concluir a iteração.
  2. Ou você pode copiar todos os que deseja manter em uma nova lista enquanto repassa e, em seguida, descartar a lista antiga quando terminar.

Essas opções pressupõem que você precise fazer uma iteração na lista para encontrar os elementos a serem removidos – útil nos casos em que os elementos da lista são objects complexos com propriedades nas quais você pode testar.

No seu caso particular, você não precisa nem repetir, pois você pode simplesmente usar o removeAll. Veja a API aqui . Há também methods bacanas, como o retentivo, que descartam tudo o que não está no argumento. Você pode usar methods remove / retain-like sempre que os objects na lista implementarem equals e hashcode corretamente. Se você não pode confiar em equals / hashcode para identificar a igualdade entre instâncias no seu aplicativo, você terá que fazer a remoção você mesmo ….

Eu acho que vale a pena mencionar a versão do Java 8

 @Test public void testListCur() { List li = new ArrayList(); for (int i = 0; i < 10; i++) { li.add("str" + i); } li = li.stream().filter(st -> !st.equalsIgnoreCase("str3")).collect(Collectors.toList()); System.out.println(li); } 

Eu tenho esse problema e acho que a maneira mais fácil é a mesma da segunda maneira que os hvgotcodes deram.

Ou você pode copiar todos os que deseja manter em uma nova lista enquanto repassa e, em seguida, descartar a lista antiga quando terminar.

 @Test public void testListCur(){ List li=new ArrayList(); for(int i=0;i<10;i++){ li.add("str"+i); } List finalLi = new ArrayList(); for(String st:li){ if(st.equalsIgnoreCase("str3")){ // Do nothing } else { finalLi.add(st); } } System.out.println(finalLi); } 

ArrayList tem campo modCount – contagem de modificações de coleção

Quando você invoca o método, o iterator() cria o novo object Itr . Tem campo expectedModCount . expectedModCount campo expectedModCount inicializado pelo valor de modCount . Quando você invoca

 li.remove("str3"); 

incrementos modCount . Quando você tenta acessar li pelo iterador, verifica que expectedModCount == modCount

e se é falso lança ConcurrentModificationException

Portanto, se você obtiver iterador e após a modificação, o iterador será considerado inválido e você não poderá usá-lo.

Tente isso (Java 8):

 list.removeIf(condition); 

Dei um jeito diferente …

 public void testListCur(){ List li=new ArrayList(); for(int i=0;i<10;i++){ li.add("str"+i); } for(int i=0; i
  • Você pode fazer uma cópia da lista da qual deseja remover o elemento, diretamente no loop for-each. Para mim, essa é a maneira mais simples. Algo assim:

     for (String stringIter : new ArrayList(myList)) { myList.remove(itemToRemove); } 

    Espero que isso te ajude ..

    Eu acho que a melhor resposta é do bigdev.de, mas eu gostaria de adicionar algo a ele (como se o item fosse removido de uma lista, talvez você queira registrar isso em algum lugar ou algo assim):

     List list = new ArrayList<>(); list.removeIf(a -> { boolean condition = a.equalsIgnoreCase("some condition"); if(condition) logger.info("Item removed from the list: " + a); return condition; });