Sobes.tech
Middle

Как да избегнете изключението ConcurrentModificationException при обхождане на колекция?

sobes.tech AI

Отговор от AI

За да избегнете ConcurrentModificationException при обхождане на колекция, можете да използвате следните подходи:

  1. Използвайте потокобезопасни колекции: java.util.concurrent предоставя потокобезопасни алтернативи на стандартните колекции. Например:

    • CopyOnWriteArrayList вместо ArrayList
    • CopyOnWriteArraySet вместо HashSet
    • ConcurrentHashMap вместо HashMap
    // Пример с 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");
    
            // Итераторът не хвърля ConcurrentModificationException
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String element = iterator.next();
                System.out.println(element);
                // Безопасно е да променяте колекцията по време на обхождане
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Крайният списък: " + list);
        }
    }
    
  2. Блокирайте колекцията: Синхронизирайте достъпа до колекцията с помощта на synchronized или блокировки java.util.concurrent.locks.

    // Пример с синхронизирана блокировка
    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) { // Блокираме колекцията за достъп
               Iterator<String> iterator = list.iterator();
               while (iterator.hasNext()) {
                   String element = iterator.next();
                   System.out.println(element);
                   // Не е позволено да променяте колекцията тук, ако друг поток може да получи достъп
                   // чрез същия `synchronized` блок
               }
           }
           // Промяната в колекцията извън синхронизирания блок или от друг поток
           // може все още да предизвика ConcurrentModificationException при паралелно обхождане
       }
    }
    
  3. Използвайте итератор за изтриване на елементи: Ако трябва да изтриете елементи по време на обхождане, използвайте метода remove() на самия итератор (ако колекцията го поддържа).

    // Пример за изтриване с използване на 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);
                // Безопасно е да изтриете елемент чрез итератора
                if (element.equals("B")) {
                    iterator.remove();
                }
            }
            System.out.println("Крайният списък: " + list);
        }
    }
    
  4. Обхождайте копие на колекцията: Създайте копие на колекцията и я обхождайте. Промените в оригиналната колекция няма да повлияят на обхождането на копието.

    // Пример за обхождане на копие
    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");
    
            // Създаваме копие за обхождане
            List<String> copyList = new ArrayList<>(list);
    
            for (String element : copyList) {
                System.out.println(element);
                // Можем да променяме оригиналната колекция по време на обхождане на копието
                if (element.equals("B")) {
                    list.remove(element);
                }
            }
            System.out.println("Крайният списък: " + list);
        }
    }
    

Изборът на подход зависи от конкретните изисквания: трябва ли да променяте колекцията по време на обхождане, колко е важна производителността и дали кодът работи в многонишкова среда. CopyOnWriteArrayList/CopyOnWriteArraySet са подходящи, когато четенето е значително по-голямо от записите. Блокирането е подходящо, когато промените се случват по-рядко, но могат да бъдат по-чести отколкото при използване на CopyOnWrite. Използването на Iterator.remove() или обхождането на копие е подходящо, когато промените се случват само в рамките на една итерация или поток.