Files
obsidian/Kotlin/Синтаксис и типы/Базовый синтаксис/Циклы и диапазоны.md
T

6.8 KiB
Raw Blame History

title, description, tags
title description tags
Циклы и диапазоны for-in, диапазоны и прогрессии, while, метки и отсутствие классического for
kotlin
syntax

В Kotlin один цикл обхода - for (x in source). Классического for (int i = 0; i < n; i++) нет: его роль выполняют диапазоны.

Диапазоны

for (i in 1..5) print(i)        // 12345 — включая 5
for (i in 1..<5) print(i)       // 1234  — исключая 5
for (i in 5 downTo 1) print(i)  // 54321
for (i in 1..10 step 3) print(i) // 1 4 7 10
for (c in 'a'..'e') print(c)    // abcde
Конструкция Смысл
a..b от a до b включительно
a..<b от a до b, не включая b
a until b то же, что a..<b (старая форма)
a downTo b по убыванию, включительно
step n с шагом n

Шаг всегда положительный, даже в downTo: направление задаёт downTo, а не знак шага.

Диапазон - это объект, его можно хранить и проверять:

val valid = 1..100
println(42 in valid)       // true
println(valid.first)       // 1
println(valid.last)        // 100
println((1..10 step 3).last) // 10

Обход по индексам

val list = listOf("a", "b", "c")

for (i in list.indices) print(i)              // 012
for (i in 0..<list.size) print(i)             // 012 — то же самое

for (item in list) print(item)                // abc — предпочтительно
for ((i, item) in list.withIndex()) {         // 0:a 1:b 2:c
    print("$i:$item ")
}

withIndex() возвращает пары, которые тут же разбираются деструктуризацией (Деструктуризация).

Обход коллекций и Map

val map = mapOf("a" to 1, "b" to 2)
for ((key, value) in map) {
    println("$key=$value")
}

for работает с любым типом, у которого есть функция iterator() - включая свои классы.

while и do-while

var i = 0
while (i < 3) {
    print(i)      // 012
    i++
}

do {
    val line = readLine()
} while (line != null)   // тело выполнится минимум один раз

Переменная, объявленная в do, видна в условии while - такого в Java нет.

break и continue

for (i in 1..10) {
    if (i == 3) continue   // пропустить итерацию
    if (i == 5) break      // выйти из цикла
    print(i)               // 124
}

Метки

Метка - идентификатор с @. Нужна, чтобы выйти из внешнего цикла:

outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (j == 2) continue@outer   // к следующей i
        if (i == 3) break@outer      // выход из обоих циклов
        print("$i$j ")               // 11 21
    }
}

Циклы - не выражения

for и while возвращают Unit, присвоить их результат нельзя. Если нужен результат обхода - это операции над коллекциями:

val doubled = (1..5).map { it * 2 }          // [2, 4, 6, 8, 10]
val evens = (1..10).filter { it % 2 == 0 }   // [2, 4, 6, 8, 10]
val sum = (1..100).sum()                     // 5050

В идиоматичном Kotlin явные циклы встречаются редко. Подробнее: Операции над коллекциями

repeat

Когда индекс не нужен:

repeat(3) { println("повтор") }
repeat(3) { i -> println("итерация $i") }   // индекс доступен, с нуля

Это не оператор, а inline-функция стандартной библиотеки.

[!info] Отличия от Java

Java Kotlin
for (int i=0; i<n; i++) for (i in 0..<n)
for (T x : collection) for (x in collection)
label: for (...) outer@ for (...)
break label; break@outer
три части в заголовке for только in
нет диапазонов 1..10, step, downTo

Что потеряли: заголовок for с несколькими переменными и произвольным условием. Если нужна такая логика - пишется while.

Сравнить: Циклы в Java

[!warning] Подводные камни 1..5 включает 5. Самая частая ошибка при переходе с Java: индексы массива - это 0..<size, а не 0..size:

val list = listOf("a", "b", "c")
// for (i in 0..list.size) println(list[i])   // IndexOutOfBounds на i=3
for (i in 0..<list.size) println(list[i])     // верно
for (i in list.indices) println(list[i])      // ещё лучше

downTo со step требует положительного шага:

for (i in 5 downTo 1 step 2) print(i)   // 531
// for (i in 5 downTo 1 step -2)        // IllegalArgumentException в рантайме

Пустой диапазон не ошибка, просто ноль итераций:

for (i in 5..1) print(i)   // ничего не напечатает

Молчаливый баг, если границы перепутаны. (5..1).isEmpty() - true.

Коллекцию нельзя изменять во время обхода:

val list = mutableListOf(1, 2, 3)
// for (x in list) if (x == 2) list.remove(x)   // ConcurrentModificationException
list.removeAll { it == 2 }                      // правильный способ

Диапазон Long и Char есть, а Double - нет. 1.0..2.0 создаст ClosedFloatingPointRange: проверять in можно, но обойти циклом - нельзя (нет iterator()).

Дополнительно: Операции над коллекциями Далее: Строковые шаблоны и вывод