Какой контракт Iterator определяет, когда допустим вызов remove после next?
Контракт Iterator разрешает вызвать remove только после успешного next и не более одного раза для возвращённого им элемента. Повторный remove без следующего next нарушает состояние итератора и обычно приводит к IllegalStateException.
Операция remove является необязательной: конкретный итератор может не поддерживать её и выбрасывать UnsupportedOperationException даже при правильном порядке вызовов.
Java Collections Framework стандартизировал единый способ последовательного обхода коллекций через интерфейс Iterator. Такой подход позволил отделить алгоритм обхода от внутреннего представления коллекции и предоставить согласованный способ удаления текущего элемента.
До этого удаление элементов во время обхода часто требовало знания структуры конкретной коллекции или приводило к ошибкам при изменении коллекции напрямую. Контракт итератора формализовал допустимую последовательность операций и связал удаление с последним элементом, возвращённым этим итератором.
Если удалять элементы коллекции напрямую во время обхода, итератор может потерять согласованность с её внутренним состоянием. Для некоторых реализаций это приводит к пропуску элементов, некорректному обходу или ConcurrentModificationException.
Даже при использовании Iterator важно соблюдать его протокол. Вызов remove до первого next или повторный вызов после уже выполненного удаления нарушает однозначность того, какой элемент нужно удалить.
Итератор можно рассматривать как автомат состояний. После создания или после удаления он находится в состоянии, где удалять нечего; успешный next переводит его в состояние, в котором разрешён один remove.
После remove итератор возвращается в состояние запрета удаления. Следующий вызов next выбирает новый текущий элемент и снова делает один вызов remove допустимым.
Здесь удаление выполняется через тот же итератор, который обнаружил элемент. Реализация может скорректировать свою позицию и служебное состояние, поэтому следующий next продолжит обход корректно.
Вызов remove до next или дважды подряд обычно завершается IllegalStateException. Если реализация не поддерживает удаление, результатом будет UnsupportedOperationException; это отдельная ситуация, не нарушение последовательности вызовов.
Поддержка операции зависит от конкретной коллекции и итератора. Кроме того, внешние структурные изменения коллекции во время обхода не заменяют iterator.remove: они могут сделать поведение неопределённым с точки зрения контракта или привести к лучшей попытке обнаружения нарушения через fail-fast-механизм.
Сервис обрабатывает список задач и должен удалить из него отменённые задачи во время одного прохода. Прямой вызов list.remove внутри обхода опасен: после структурного изменения итератор может обнаружить несогласованность или пропустить часть элементов.
Возможны три варианта. Создать новый список с нужными задачами безопасно и часто проще, но это требует дополнительной памяти. Вызвать removeIf выразительно и обычно предпочтительно для простого предиката, однако оно не подходит, если во время обхода требуется сложная пошаговая логика. Использовать явный Iterator немного многословнее, зато явно контролировать порядок next и remove.
Для случая со сложной логикой выбирается явный итератор. Удаление выполняется через него, поэтому реализация коллекции может согласованно обновить позицию обхода; отдельная копия списка не нужна, а риск некорректного прямого изменения устраняется.
remove сразу после создания итератора?Нет. До успешного next итератор не имеет элемента, который он обязан удалить, поэтому такой вызов нарушает его контракт и обычно приводит к IllegalStateException. Вызов hasNext это состояние не меняет: он только проверяет наличие следующего элемента и не возвращает его.
Нет. remove — необязательная операция интерфейса. Итератор может корректно реализовывать next, но выбрасывать UnsupportedOperationException при попытке удаления. Поэтому код должен учитывать документацию конкретной коллекции и не смешивать отсутствие поддержки операции с неправильным порядком вызовов.
Каждый remove относится только к элементу, возвращённому последним успешным next. После удаления этот элемент уже обработан, а нового текущего элемента ещё нет. Чтобы удалить следующий элемент, нужно снова вызвать next, проверить его результат и только затем вызвать remove; иначе будет нарушен контракт и обычно возникнет IllegalStateException.