Avatar ·

Как получить текущий индекс элемента в цикле forEach в Kotlin?

Привет, коллеги! Я только начал изучать Kotlin и столкнулся с проблемой при работе с циклами. В Java я привык использовать обычный for с индексом, например: ```java for (int i = 0; i < list.size(); i++) { System.out.println(i + ": " + list.get(i)); } ``` Но в Kotlin я вижу, что все рекомендуют использовать forEach: ```kotlin list.forEach { item -> println(item) } ``` Это удобно, но что если мне нужен не только сам элемент, но и его индекс? Например, я хочу вывести список задач с номерами: ```kotlin val tasks = listOf("Купить хлеб", "Помыть посуду", "Сделать уроки") tasks.forEach { task -> println("$index: $task") // Ошибка! index не определён } ``` Здесь я получаю ошибку компиляции, потому что переменная index не существует. Я пробовал завести отдельный счётчик: ```kotlin var index = 0 tasks.forEach { task -> println("$index: $task") index++ } ``` Но это выглядит костыльно и некрасиво. К тому же, если я использую параллельные коллекции, mutable переменная вообще приведёт к багам. Подскажите, как правильно получить индекс элемента внутри forEach? Может быть, есть встроенная функция? Или нужно использовать другой цикл? Буду благодарен за примеры кода! P.S. Пишу под Android, так что интересуют решения без лишних зависимостей. <<>>

Использование withIndex() — стандартное решение

Самый простой и идиоматичный способ получить индекс в Kotlin — использовать функцию-расширение withIndex(). Она возвращает коллекцию объектов IndexedValue, каждый из которых содержит index и value:

val tasks = listOf("Купить хлеб", "Помыть посуду", "Сделать уроки")

tasks.withIndex().forEach { (index, task) ->
    println("$index: $task")
}
// Вывод:
// 0: Купить хлеб
// 1: Помыть посуду
// 2: Сделать уроки

Обратите внимание на деструктуризацию: (index, task) — это удобный способ сразу разложить IndexedValue на составляющие. Если вам не нравится синтаксис, можно написать явно:

tasks.withIndex().forEach { indexedValue ->
    println("${indexedValue.index}: ${indexedValue.value}")
}

Альтернатива: forEachIndexed()

Для тех, кто предпочитает более прямой вызов, существует встроенная функция forEachIndexed. Она принимает лямбду с двумя параметрами: индекс и элемент:

tasks.forEachIndexed { index, task ->
    println("$index: $task")
}

Это эквивалентно предыдущему решению, но короче. Выбирайте то, что вам удобнее.

Почему не стоит использовать внешний счётчик?

Ваш вариант с var index = 0 работает, но имеет два недостатка:

  • Проблемы с параллельной обработкой. Если вы замените forEach на parallelStream или forEach на коллекции из Java, mutable переменная вызовет состояние гонки.
  • Неидиоматичность. В Kotlin принято избегать мутабельных переменных там, где можно обойтись без них. Это делает код чище и безопаснее.

Если нужен только индекс (без элемента)

Иногда требуется только индекс, например, для обновления элементов. Используйте indices:

for (i in tasks.indices) {
    println("Индекс $i")
}

Или forEachIndexed с игнорированием элемента:

tasks.forEachIndexed { index, _ ->
    println("Индекс $index")
}

Для массивов

Все те же функции работают и с массивами:

val array = arrayOf("A", "B", "C")
array.withIndex().forEach { (i, v) -> println("$i: $v") }
array.forEachIndexed { i, v -> println("$i: $v") }

Итог: для получения индекса в forEach используйте withIndex() или forEachIndexed. Это стандартные, безопасные и читаемые решения, принятые в Kotlin-сообществе.

<<>>

Старый добрый for с range

Если вам привычнее классический цикл или нужно больше контроля (например, шаг, обратный порядок), можно использовать for с диапазоном индексов:

val tasks = listOf("Купить хлеб", "Помыть посуду", "Сделать уроки")
for (i in tasks.indices) {
    println("$i: ${tasks[i]}")
}

Здесь indices возвращает диапазон 0..size-1. Это работает и для массивов. Если нужен доступ к элементу через геттер, используйте tasks[i].

Когда это может быть удобнее?

  • Когда нужно модифицировать элементы по индексу: tasks[i] = tasks[i].uppercase().
  • Когда требуется обратный проход: for (i in tasks.lastIndex downTo 0).
  • Когда нужно пропускать элементы: for (i in 0 until tasks.size step 2).

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