Как получить индекс элемента в цикле forEach в Kotlin?
Использование withIndex() — самый правильный способ
В Kotlin есть встроенная функция withIndex(), которая возвращает коллекцию объектов IndexedValue. Каждый такой объект содержит два поля: index (Int) и value (тип элемента). Это идиоматичный и безопасный способ получить индекс внутри forEach.
val list = listOf("Яблоко", "Банан", "Вишня")
list.withIndex().forEach { (index, item) ->
println("Индекс $index: $item")
}
// Вывод:
// Индекс 0: Яблоко
// Индекс 1: Банан
// Индекс 2: Вишня
Обратите внимание на деструктуризацию в лямбде: (index, item) — это удобно и читаемо. Вы также можете использовать более явный синтаксис:
list.withIndex().forEach { indexedValue ->
println("Индекс ${indexedValue.index}: ${indexedValue.value}")
}
Альтернатива: forEachIndexed
Есть ещё более короткий вариант — функция-расширение forEachIndexed. Она принимает лямбду с двумя параметрами: индекс и значение.
list.forEachIndexed { index, item ->
println("Индекс $index: $item")
}
Это, пожалуй, самый лаконичный способ. Но если вам нужно не просто выполнить действие, а получить новую коллекцию (например, отфильтровать или преобразовать), лучше использовать withIndex() в связке с другими функциями.
Примеры для реальных задач
- Проверка, является ли элемент последним:
list.withIndex().forEach { (index, item) ->
val isLast = index == list.lastIndex
println("$item ${if (isLast) "(последний)" else ""}")
}
- Создание списка с номерами (для RecyclerView):
data class Task(val title: String, val number: Int)
val tasks = listOf("Купить хлеб", "Помыть посуду", "Сделать ДЗ")
val numberedTasks = tasks.withIndex().map { (index, title) ->
Task(title = title, number = index + 1)
}
// Result: [Task(title=Купить хлеб, number=1), Task(title=Помыть посуду, number=2), ...]
- Фильтрация по индексу (например, каждый второй элемент):
val evenIndexedItems = list.withIndex()
.filter { (index, _) -> index % 2 == 0 }
.map { (_, item) -> item }
// Result: [Яблоко, Вишня]
Почему ваш ручной счётчик — плохая идея?
Ваш вариант с var index = 0 и index++ работает, но нарушает принципы функционального программирования, к которым стремится Kotlin. Проблемы:
- Мутабельная переменная (var) — это источник ошибок в многопоточном коде.
- Если внутри цикла использовать
return(из лямбды с меткой) илиbreak(черезrun), счётчик собьётся. - Код менее читаем: нужно помнить, что index объявлен снаружи.
Используйте withIndex() или forEachIndexed — они чище, безопаснее и короче.
Как быть с break/continue?
В обычном forEach нельзя использовать break или continue. Если вам нужен досрочный выход по индексу, используйте обычный цикл for:
for (i in list.indices) {
if (i == 1) continue // пропустить индекс 1
if (i == 3) break // остановиться на индексе 3
println("$i: ${list[i]}")
}
Но если вы всё же хотите остаться в функциональном стиле, используйте takeWhile или first — это более идиоматично, чем break.
Использование mapIndexed для преобразования
Если ваша цель — не просто выполнить действие с элементом, а создать новую коллекцию на основе исходной с учётом индекса, mapIndexed будет удобнее, чем withIndex() + map.
val list = listOf("A", "B", "C")
val indexedList = list.mapIndexed { index, item ->
"[$index] $item"
}
// Результат: ["[0] A", "[1] B", "[2] C"]
То же самое для фильтрации — используйте filterIndexed:
val oddIndexed = list.filterIndexed { index, _ -> index % 2 == 1 }
// Результат: ["B"]
Эти функции работают по тому же принципу, что и forEachIndexed, но возвращают новую коллекцию, а не Unit. Это делает код более декларативным и цепочным.
Когда какой способ выбрать?
- Просто вывести индекс →
forEachIndexed(коротко и ясно). - Преобразовать коллекцию →
mapIndexed. - Отфильтровать с учётом индекса →
filterIndexed. - Нужен объект с индексом для передачи в другую функцию →
withIndex()(возвращаетIterable).
Совет: не используйте withIndex() только ради forEach — для этого есть forEachIndexed. Но если вам нужно несколько операций (фильтрация + маппинг + forEach), withIndex() позволяет сделать это в одной цепочке без потери информации об индексе.