При обходе списка через ListIterator требуется заменить текущий элемент. Какая семантика операции set позволяет сделать это без структурного изменения списка?
ListIterator.set заменяет значение последнего элемента, возвращённого next() или previous(), не изменяя размер и структуру списка. Поэтому такая замена обычно не нарушает состояние итератора и не считается структурным изменением, в отличие от добавления или удаления элементов.
Коллекции должны поддерживать не только чтение при обходе, но и контролируемое изменение списка прямо во время итерации. Для этого в Java существует ListIterator — специализированный итератор для списков, умеющий двигаться в обоих направлениях и выполнять операции set, add и remove.
Разделение замены значения и структурных операций позволяет изменять содержимое списка без перестройки его структуры. Это особенно важно для массивоподобных списков, где изменение элемента по существующей позиции значительно дешевле вставки или удаления.
Если во время обхода изменять список напрямую, итератор может обнаружить неожиданное структурное изменение. Для fail-fast-реализаций это способно привести к ConcurrentModificationException, а для других реализаций — к пропуску или повторной обработке элементов.
Однако не всякое изменение элемента является структурным. Ошибка кандидата — считать любую замену изменением структуры или пытаться заменить элемент через добавление и удаление. Это усложняет код, может изменить размер списка и нарушить ожидаемую позицию итератора.
Операция set заменяет элемент, который был последним возвращён текущим итератором методами next() или previous(). Она не меняет размер списка, порядок позиций или связи между элементами, поэтому не считается структурным изменением.
Вызвать set до первого next() или previous() нельзя. Повторный set без нового перемещения обычно допустим: он снова изменяет тот же последний возвращённый элемент. После add или remove право на set для прежнего элемента теряется, пока итератор снова не вернёт элемент через next() или previous().
set не гарантирует потокобезопасность. Если другой поток одновременно изменяет список, обычный ListIterator не превращается в безопасный итератор. Кроме того, конкретный список может иметь ограничения на замену элементов: например, неизменяемый список отклонит такую операцию, а список фиксированного размера вроде представления массива обычно допускает замену, но не изменение размера.
Практическое преимущество set — сохранение текущей позиции обхода и отсутствие сдвига остальных элементов. Если же требуется вставить новый элемент или удалить существующий, нужно использовать соответствующие методы самого ListIterator, соблюдая их контракт.
Сервис обрабатывает список нормализуемых строк и должен заменить каждую строку на очищенную версию во время одного прохода. Рассматривались три варианта.
list.set(index, value): работает, но требует отдельного счётчика индекса и осторожности при нетипичном обходе.ListIterator.set: непосредственно заменяет последний возвращённый элемент и сохраняет состояние обхода.Выбран третий вариант. Он точнее выражает намерение, не меняет размер списка и не вызывает структурного изменения. Если список передан как неизменяемое представление, сначала нужно выбрать изменяемую копию; сам set не снимает ограничений исходной коллекции.
1. Можно ли вызвать set сразу после создания ListIterator?
Нет. До первого успешного next() или previous() у итератора нет элемента, который можно считать последним возвращённым. Вызов set в этот момент нарушает контракт и приводит к IllegalStateException.
2. Чем set отличается от add с точки зрения структуры списка?
set сохраняет размер и существующие позиции, меняя только значение элемента. add вставляет новый элемент, увеличивает размер и изменяет структуру списка; после него итератор корректирует свою позицию, но прежнее право на set теряется.
3. Безопасен ли ListIterator.set при одновременной работе нескольких потоков?
Нет, сам по себе ListIterator не обеспечивает синхронизацию. Если несколько потоков обращаются к изменяемому списку, нужна внешняя координация или подходящая concurrent-структура; при этом не каждая concurrent-коллекция предоставляет семантику ListIterator.set. Fail-fast-поведение обычного итератора также не является механизмом потокобезопасности.