Sobes.tech
Middle

Ako sa vyhnúť výnimke ConcurrentModificationException pri prechádzaní kolekcie?

sobes.tech AI

Odpoveď od AI

Aby ste sa vyhli ConcurrentModificationException pri prechádzaní kolekcie, môžete použiť nasledujúce prístupy:

  1. Používajte vlákno-bezpečné kolekcie: java.util.concurrent poskytuje vlákno-bezpečné alternatívy štandardných kolekcií. Napríklad:

    • CopyOnWriteArrayList namiesto ArrayList
    • CopyOnWriteArraySet namiesto HashSet
    • ConcurrentHashMap namiesto HashMap
    // Príklad s 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");
    
            // Iterátor nehodí `ConcurrentModificationException`
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Bezpečne meniť kolekciu počas prechádzania
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Konečný zoznam: " + list);
        }
    }
    
  2. Zamknite kolekciu: Synchronizujte prístup ku kolekcii pomocou synchronized alebo zámkov java.util.concurrent.locks.

    // Príklad so synchronizovaným blokom
    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) { // Zamknutie kolekcie pre prístup
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // Nie je povolené meniť kolekciu tu, ak iný vlákno môže získať prístup
                   // cez tento `synchronized` blok
               }
           }
           // Zmena kolekcie mimo synchronizovaného bloku alebo iným vláknom
           // môže stále spôsobiť `ConcurrentModificationException` pri paralelnom prechádzaní
       }
    }
    
  3. Používajte iterátor na odstraňovanie prvkov: Ak potrebujete odstraňovať prvky počas prechádzania, použite metódu remove() samotného iterátora (ak ho kolekcia podporuje).

    // Príklad odstraňovania pomocou `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);
                // Bezpečne odstrániť prvok cez iterátor
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Konečný zoznam: " + list);
        }
    }
    
  4. Prechádzajte kópiu kolekcie: Vytvorte kópiu kolekcie a prechádzajte ju. Zmeny v pôvodnej kolekcii nebudú mať vplyv na prechádzanie kópie.

    // Príklad prechádzania kópie
    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");
    
            // Vytvorenie kópie na prechádzanie
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Môžeme meniť pôvodnú kolekciu počas prechádzania kópie
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Konečný zoznam: " + list);
        }
    }
    

Výber prístupu závisí od konkrétnych požiadaviek: či je potrebné meniť kolekciu počas prechádzania, aký je význam výkonu a či kód beží v viacvláknovej prostredí. CopyOnWriteArrayList/CopyOnWriteArraySet sú vhodné, keď je čítanie výrazne väčšie ako zápis. Zamknutie je vhodnejšie, keď sa zmeny dejú zriedkavo, ale môžu byť častejšie než pri použití CopyOnWrite. Použitie Iterator.remove() alebo prechádzanie kópie je vhodné, keď sa zmeny dejú iba v rámci jednej iterácie alebo vlákna.