Sobes.tech
Middle

Como evitar a exceção ConcurrentModificationException ao percorrer uma coleção?

sobes.tech IA

Resposta da IA

Para evitar ConcurrentModificationException ao percorrer uma coleção, pode usar as seguintes abordagens:

  1. Usar coleções seguras para threads: java.util.concurrent fornece alternativas seguras para threads às coleções padrão. Por exemplo:

    • CopyOnWriteArrayList em vez de ArrayList
    • CopyOnWriteArraySet em vez de HashSet
    • ConcurrentHashMap em vez de HashMap
    // Exemplo com CopyOnWriteArrayList
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.Iterator;
    
    public class ConcurrentCollectionExample {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // O iterador não lança ConcurrentModificationException
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Alterar a coleção durante a iteração é seguro
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    
  2. Bloquear a coleção: Sincronizar o acesso à coleção usando synchronized ou bloqueios java.util.concurrent.locks.

    // Exemplo com bloco sincronizado
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class SynchronizedCollectionExample {
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("A");
           list.add("B");
           list.add("C");
    
           synchronized (list) { // Bloqueia a coleção para acesso
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // Não é possível modificar a coleção aqui se outro thread puder acessá-la
                   // através deste mesmo bloco synchronized
               }
           }
           // Modificar a coleção fora do bloco sincronizado ou em outro thread
           // ainda pode causar ConcurrentModificationException durante a iteração paralela
       }
    }
    
  3. Usar o iterador para remover elementos: Se precisar remover elementos durante a iteração, use o método remove() do próprio iterador (se suportado pela coleção).

    // Exemplo de remoção usando Iterator.remove()
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class IteratorRemoveExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Remover elemento através do iterador de forma segura
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    
  4. Percorrer uma cópia da coleção: Criar uma cópia da coleção e percorrê-la. As alterações na coleção original não afetarão a iteração sobre a cópia.

    // Exemplo de percorrer uma cópia
    import java.util.ArrayList;
    import java.util.List;
    
    public class CopyIterationExample {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
    
            // Criar uma cópia para percorrer
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Podemos modificar a coleção original durante a iteração da cópia
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Lista final: " + list);
        }
    }
    

A escolha da abordagem depende dos requisitos específicos: se precisa modificar a coleção durante a iteração, quão importante é o desempenho e se o código funciona em um ambiente multithread. CopyOnWriteArrayList/CopyOnWriteArraySet são adequados quando há muitas mais leituras do que gravações. O bloqueio é apropriado quando as mudanças ocorrem com menos frequência, mas podem ser mais frequentes do que com CopyOnWrite. O uso de Iterator.remove() ou percorrer uma cópia é útil quando as mudanças ocorrem apenas dentro de uma única iteração ou thread.