Как правильно удалять элементы из коллекции при итерации
Что проверяют
Знаете ли вы, откуда берётся ConcurrentModificationException и какой способ удаления выбрать в Kotlin на Android. Следом часто спрашивают, почему удаление по индексу в цикле пропускает элементы и что делать, если список меняет другой поток.
Как отвечать
Причина исключения: итераторы ArrayList и HashMap работают в режиме fail-fast. Коллекция считает изменения в modCount, итератор запоминает значение при создании и сверяет его на каждом next(). Цикл for (item in list) идёт через итератор, поэтому list.remove(item) внутри него ломает обход даже в одном потоке.
Правильные способы:
removeAll { }дляMutableList: удаляет по условию и работает на любом уровне API. ДжавовыйremoveIfна Android доступен с API 24;MutableIterator.remove(): когда при удалении нужна дополнительная логика, например освободить ресурс. Вызывается не больше одного раза после каждогоnext();- новая коллекция:
filter { }илиfilterNot { }. В Android-коде это часто лучший вариант, потому что UI-слою и так нужен неизменяемый список; - обратный проход по индексам:
for (i in list.indices.reversed()), если индексы действительно нужны. Прямой проход после удаления сдвигает элементы и пропускает соседа; CopyOnWriteArrayList: для списка слушателей, который читают часто, а меняют редко. Итерация идёт по снимку, но каждое изменение копирует массив.
Для состояния экрана: в StateFlow кладут новый список через _state.update { list -> list.filterNot { it.id == id } }, в ListAdapter передают новый экземпляр через submitList().
Где спотыкаются
Считают CME проблемой только многопоточности. Чаще всего его ловят как раз в одном потоке, а от гонок между потоками fail-fast вообще не гарантирует защиты.
Проверяют код на удалении предпоследнего элемента ArrayList и не видят исключения: hasNext() возвращает false, цикл просто заканчивается, и последний элемент тихо остаётся непроверенным.
Мутируют список, который уже отдан адаптеру или лежит в mutableStateOf. submitList() с тем же экземпляром ничего не делает, Compose не видит изменений внутри обычного MutableList, и экран не обновляется.