← Назад к вопросам

Какие ошибки вызывает изменение данных при итерировании

1.7 Middle🔥 221 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ошибки при изменении данных во время итерирования

Одна из классических ошибок в Java — модификация коллекции (добавление, удаление элементов) во время её итерирования. Это приводит к исключениям и непредсказуемому поведению.

1. ConcurrentModificationException

Основная ошибка при изменении коллекции во время итерирования:

import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        // ❌ ОШИБКА: ConcurrentModificationException
        for (String name : names) {
            if (name.equals("Bob")) {
                names.remove(name);  // Модификация во время итерирования!
            }
        }
    }
}

Результат:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)

2. Как это происходит внутри

Механизм обнаружения:

public class IteratorMechanism {
    static class MyIterator<E> {
        private int expectedModCount;  // Запомняем модификации
        private int currentModCount;
        
        public MyIterator(List<E> list) {
            // При создании iterator запоминаем текущий modCount
            this.expectedModCount = list.modCount;
        }
        
        public E next() {
            // Перед каждым next() проверяем не изменилась ли коллекция
            if (expectedModCount != currentModCount) {
                throw new ConcurrentModificationException();
            }
            // ... вернуть следующий элемент
            return null;
        }
    }
}

Каждая модификация ArrayList увеличивает внутренний счётчик modCount. Iterator проверяет этот счётчик перед каждой операцией.

3. Правильные способы удаления элементов

Способ 1: Использовать iterator.remove()

public class SafeRemovalUsingIterator {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        // ✅ ПРАВИЛЬНО: использовать iterator.remove()
        for (Iterator<String> iterator = names.iterator(); iterator.hasNext();) {
            String name = iterator.next();
            if (name.equals("Bob")) {
                iterator.remove();  // Безопасное удаление
            }
        }
        
        System.out.println(names);  // [Alice, Charlie]
    }
}

Почему это работает? Iterator.remove() синхронизирует свой modCount с коллекцией, поэтому исключение не выбрасывается.

Способ 2: Stream API (функциональный подход)

import java.util.stream.Collectors;

public class SafeRemovalUsingStreams {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        // ✅ ПРАВИЛЬНО: использовать stream для фильтрации
        List<String> filtered = names.stream()
            .filter(name -> !name.equals("Bob"))
            .collect(Collectors.toList());
        
        System.out.println(filtered);  // [Alice, Charlie]
    }
}

Способ 3: Создать временный список

import java.util.ArrayList;
import java.util.List;

public class SafeRemovalUsingTempList {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        // ✅ ПРАВИЛЬНО: найти элементы в отдельном списке, потом удалить
        List<String> toRemove = new ArrayList<>();
        for (String name : names) {
            if (name.equals("Bob")) {
                toRemove.add(name);
            }
        }
        
        names.removeAll(toRemove);  // Удаляем все из списка toRemove
        System.out.println(names);  // [Alice, Charlie]
    }
}

Способ 4: removeIf() (Java 8+)

public class SafeRemovalUsingRemoveIf {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        // ✅ ПРАВИЛЬНО: removeIf() безопасен
        names.removeIf(name -> name.equals("Bob"));
        
        System.out.println(names);  // [Alice, Charlie]
    }
}

4. Другие ошибки при модификации

Добавление элементов:

public class AddDuringIteration {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        
        // ❌ ОШИБКА: ConcurrentModificationException
        for (int num : numbers) {
            if (num == 2) {
                numbers.add(4);  // Модификация во время итерирования!
            }
        }
    }
}

Clear коллекции:

public class ClearDuringIteration {
    public static void main(String[] args) {
        List<String> items = new ArrayList<>();
        items.add("item1");
        items.add("item2");
        
        // ❌ ОШИБКА: ConcurrentModificationException
        for (String item : items) {
            items.clear();  // Очистили всю коллекцию!
        }
    }
}

5. Проблемы с Map'ами

EntrySet итерирование:

import java.util.HashMap;
import java.util.Map;

public class MapModificationError {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 85);
        scores.put("Bob", 90);
        scores.put("Charlie", 78);
        
        // ❌ ОШИБКА: ConcurrentModificationException
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            if (entry.getValue() < 80) {
                scores.remove(entry.getKey());  // Модификация!
            }
        }
    }
}

Правильный способ для Map:

public class SafeMapRemoval {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 85);
        scores.put("Bob", 90);
        scores.put("Charlie", 78);
        
        // ✅ ПРАВИЛЬНО: использовать iterator
        Iterator<Map.Entry<String, Integer>> iterator = 
            scores.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            if (entry.getValue() < 80) {
                iterator.remove();  // Безопасное удаление
            }
        }
        
        System.out.println(scores);  // {Alice=85, Bob=90}
    }
}

Или с removeIf:

public class MapRemoveIfExample {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 85);
        scores.put("Bob", 90);
        scores.put("Charlie", 78);
        
        // ✅ ПРАВИЛЬНО: removeIf для Map
        scores.entrySet().removeIf(entry -> entry.getValue() < 80);
        
        System.out.println(scores);  // {Alice=85, Bob=90}
    }
}

6. ConcurrentModificationException в разных сценариях

Проблема с nested итерирование:

public class NestedIterationError {
    public static void main(String[] args) {
        List<List<Integer>> matrix = new ArrayList<>();
        matrix.add(new ArrayList<>(java.util.Arrays.asList(1, 2, 3)));
        matrix.add(new ArrayList<>(java.util.Arrays.asList(4, 5, 6)));
        
        // ❌ ОШИБКА: изменение внешней коллекции во внутреннем loop
        for (List<Integer> row : matrix) {
            for (int val : row) {
                if (val == 5) {
                    matrix.remove(row);  // Модификация внешней коллекции!
                }
            }
        }
    }
}

7. Thread-safe коллекции

ConcurrentHashMap не выбрасывает ConcurrentModificationException:

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ConcurrentMapModification {
    public static void main(String[] args) {
        Map<String, Integer> map = new ConcurrentHashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);
        
        // ✓ ConcurrentHashMap позволяет некоторые модификации
        // но результат может быть непредсказуем
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            if (entry.getValue() == 2) {
                map.put("d", 4);  // Не выбросит исключение
                // Но может пропустить новый элемент в итерировании
            }
        }
    }
}

Чек-лист: как избежать ошибок

✗ Не удаляй/добавляй элементы в for-each loop
✓ Используй iterator.remove() для удаления
✓ Используй removeIf() для фильтрации
✓ Используй stream().filter().collect() для функционального подхода
✓ Создавай временный список для сбора элементов
✓ Для Map используй iterator или removeIf на entrySet()
✗ Не изменяй коллекцию в nested итерировании
✓ Используй ConcurrentHashMap если нужна конкурентность

Резюме

ConcurrentModificationException выбрасывается когда коллекция модифицируется во время итерирования. Это механизм защиты от ошибок, которые могут привести к потере данных или непредсказуемому поведению.

Решения:

  1. iterator.remove() — самый надёжный способ
  2. removeIf() — современный и функциональный подход
  3. Stream API — для функционального стиля
  4. Временный список — когда нужны более сложные условия

Понимание этого механизма критично для написания надёжного Java кода.

Какие ошибки вызывает изменение данных при итерировании | PrepBro