image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Aelmajan Azevedo
Aelmajan Azevedo06/09/2023 13:24
Compartilhe

Evitando a ConcurrentModificationException em Java

  • #Java

O problema de receber uma ConcurrentModificationException. 

Ao manipular coleções em Java, você muitas vezes você pode se deparar com essa exceção o que causa confusão em muita gente. Como pode uma aplicação single threaded lançar uma exceção sobre concorrência? A resposta para isso é que a concorrência aqui se refere a tentar por exemplo remover um elemento enquanto ao mesmo tempo se itera em uma lista.

Entendendo de forma fácil a ConcurrentModificationException

Esse é um mecanisco chamado de fast fail iterators por perceberem quando uma modificação concorrente é detectada, utilizado propositalmente em aplicações single threaded como uma tentativa de encontrar/ evitar bugs e comportamentos estranhos, como iterar em uma lista que acabou de ser modificada e isso pode evitar vários problemas. É daí que vem a exceção.

Exemplo prático de como lançar essa exceção

public void removeNamedItem(String name){
  for(Item item : this.items){
      if(item.getName().equalsIgnoreCase(name)){
          items.remove(item);
      }
  }
}

Esse método recebe como paramêtro o nome de um item a ser removido da lista. Então o método faz uma varredura na lista e verifica se o nome do item na lista corresponde ao nome que foi passado por prarâmetro. Se o item indicado estiver na última posição na lista não haverá problema algum, agora se o item estiver no início ou em qualquer lugar no meio da lista a exceção ConcurrentModificationException será lançada.

Soluções para o problema

Usar um Iterator diretamente

Aqui abrimos mão da simplicidade do for each para ter acesso ao método remove() invocado pelo iterator

public void removeByNameWithIterator(String name){
  for(Iterator<Item> iterator = items.iterator(); iterator.hasNext();){
      Item item = iterator.next();
      if(item.getName().equalsIgnoreCase(name)){
          iterator.remove();
      }
  }
}

O segredo aqui é usar o método remove() que o iterator tem acesso. Esse método é seguro para se usar enquanto se itera e não lança ConcurrentModificationException.

Iterar primeiro depois remover

Nesse caso fazemos uso do método removeAll(), que recebe uma coleção de elementos. Aqui fazemos a iteração na lista armazenando os elementos que atingem os critérios de filtragem impostos em uma lista secundária chamada toRemove e posteriormente utilizamos o metodo removelAll() para remover os elementos.

public void removeAllByName(String name){
  List<Item> toRemove = new ArrayList<>();
  for(Item item : items){
      if(item.getName().equalsIgnoreCase(name)){
          toRemove.add(item);
      }
  }
  items.removeAll(toRemove);
}

Usando o removeIf()

Com o Java 8 foi introduzido o metodo removeIf() no qual podemos fazer uso de uma forma declarativa para chegar ao mesmo resultado com menos verbosidade.

public void removeNamedItemRevamped(String name){
  items.removeIf(item -> item.getName().equalsIgnoreCase(name));
}

Aqui montamos um predicado substituindo o for each por uma variável seguida do operador de seta e o filtro que querremos aplicar.

Considerações finais

Existem uma opção fazendo uso de streams mas vou deixar para talvez um próximo artigo. O Java é uma linguagem muito madura e disponibiliza muitos mecanismos para se chegar a solução desejada. Sigamos aprendendo juntos nessa jornada.

Compartilhe
Comentários (2)
Aelmajan Azevedo
Aelmajan Azevedo - 06/09/2023 15:47

Obrigado Wellington, fico muito feliz com o seu comentário.

WF

Wellington Félix - 06/09/2023 13:54

Excelente conteúdo, bem explicado.