Iterator.remove в Java: удаление элементов во время обхода

Iterator.remove в Java: удаление элементов во время обхода
Содержание статьи

Чтобы удалить текущий элемент изменяемого ArrayList во время обхода, используйте remove() того итератора, который выдал элемент через next(). На Mentoring.digital разберём этот контракт на списке чисел и отдельно проверим ошибочные последовательности вызовов.

Обход и удаление должны использовать один итератор

Возьмём список, в котором нужно оставить только неотрицательные числа. Iterator хранит состояние обхода, а его remove удаляет последний выданный элемент.

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

public class RemoveNegative {
    public static void main(String[] args) {
        List<Integer> values = new ArrayList<>(
            List.of(3, -1, -2, 5, 0)
        );
        Iterator<Integer> iterator = values.iterator();

        while (iterator.hasNext()) {
            int value = iterator.next();
            if (value < 0) {
                iterator.remove();
            }
        }
        System.out.println(values); // [3, 5, 0]
    }
}

Вложенные отрицательные значения здесь соседствуют намеренно. Решение не пропускает второе из них из-за ручного увеличения индекса. Обёртка ArrayList нужна потому, что список, возвращаемый List.of, не поддерживает изменение.

Когда remove вызывает исключение

Вызов разрешён только после next и не более одного раза для последнего выданного элемента. Вызов до первого next или повторный remove без нового next приводит к IllegalStateException, если итератор поддерживает удаление.

Удаление является необязательной операцией интерфейса Iterator. Итератор конкретной коллекции может выбросить UnsupportedOperationException. Наличие метода в интерфейсе не означает, что любой источник данных можно менять этим способом.

Почему изменение списка снаружи мешает обходу

Не вызывайте values.remove(...) параллельно с этим обходом и не изменяйте список через второй итератор. Общий контракт Iterator не определяет поведение при внешнем изменении коллекции, если её документация не задаёт отдельную политику.

Исключение ConcurrentModificationException в знакомом цикле нельзя использовать как механизм синхронизации или обязательную проверку каждой ошибки. Сам итератор не делает коллекцию безопасной для одновременной записи из разных потоков.

Практическая проверка контракта

Проверьте пустой список, список без отрицательных чисел и список только из отрицательных. Затем отдельно, на новом списке, вызовите remove до next и дважды после одного next. Сопоставьте результат с последовательностью вызовов, а не только с текстом исключения.

Для простого удаления по условию рассмотрите также removeIf, а Iterator оставьте для случаев, когда логика обхода требует явного управления. Подобрать подход можно на практических занятиях по Java и обсудить с ментором по Java.

Похожие статьи по данной теме