160 lines
6.8 KiB
Markdown
160 lines
6.8 KiB
Markdown
---
|
||
title: Циклы и диапазоны
|
||
description: for-in, диапазоны и прогрессии, while, метки и отсутствие классического for
|
||
tags:
|
||
- kotlin
|
||
- syntax
|
||
---
|
||
|
||
В Kotlin один цикл обхода - `for (x in source)`. Классического `for (int i = 0; i < n; i++)` нет: его роль выполняют диапазоны.
|
||
|
||
## Диапазоны
|
||
```kotlin
|
||
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`, а не знак шага.
|
||
|
||
Диапазон - это объект, его можно хранить и проверять:
|
||
```kotlin
|
||
val valid = 1..100
|
||
println(42 in valid) // true
|
||
println(valid.first) // 1
|
||
println(valid.last) // 100
|
||
println((1..10 step 3).last) // 10
|
||
```
|
||
|
||
## Обход по индексам
|
||
```kotlin
|
||
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()` возвращает пары, которые тут же разбираются деструктуризацией ([Деструктуризация](../Работа%20с%20данными/Деструктуризация.md)).
|
||
|
||
## Обход коллекций и Map
|
||
```kotlin
|
||
val map = mapOf("a" to 1, "b" to 2)
|
||
for ((key, value) in map) {
|
||
println("$key=$value")
|
||
}
|
||
```
|
||
`for` работает с любым типом, у которого есть функция `iterator()` - включая свои классы.
|
||
|
||
## while и do-while
|
||
```kotlin
|
||
var i = 0
|
||
while (i < 3) {
|
||
print(i) // 012
|
||
i++
|
||
}
|
||
|
||
do {
|
||
val line = readLine()
|
||
} while (line != null) // тело выполнится минимум один раз
|
||
```
|
||
Переменная, объявленная в `do`, видна в условии `while` - такого в Java нет.
|
||
|
||
## break и continue
|
||
```kotlin
|
||
for (i in 1..10) {
|
||
if (i == 3) continue // пропустить итерацию
|
||
if (i == 5) break // выйти из цикла
|
||
print(i) // 124
|
||
}
|
||
```
|
||
|
||
## Метки
|
||
Метка - идентификатор с `@`. Нужна, чтобы выйти из внешнего цикла:
|
||
```kotlin
|
||
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`, присвоить их результат нельзя. Если нужен результат обхода - это операции над коллекциями:
|
||
```kotlin
|
||
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 явные циклы встречаются редко. Подробнее: [Операции над коллекциями](../../Коллекции/Операции.md)
|
||
|
||
## repeat
|
||
Когда индекс не нужен:
|
||
```kotlin
|
||
repeat(3) { println("повтор") }
|
||
repeat(3) { i -> println("итерация $i") } // индекс доступен, с нуля
|
||
```
|
||
Это не оператор, а [inline-функция](../../Функциональный%20Kotlin/Inline-функции.md) стандартной библиотеки.
|
||
|
||
> [!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](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Циклы.md)
|
||
|
||
> [!warning] Подводные камни
|
||
> **`1..5` включает 5.** Самая частая ошибка при переходе с Java: индексы массива - это `0..<size`, а не `0..size`:
|
||
> ```kotlin
|
||
> 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` требует положительного шага:**
|
||
> ```kotlin
|
||
> for (i in 5 downTo 1 step 2) print(i) // 531
|
||
> // for (i in 5 downTo 1 step -2) // IllegalArgumentException в рантайме
|
||
> ```
|
||
>
|
||
> **Пустой диапазон не ошибка, просто ноль итераций:**
|
||
> ```kotlin
|
||
> for (i in 5..1) print(i) // ничего не напечатает
|
||
> ```
|
||
> Молчаливый баг, если границы перепутаны. `(5..1).isEmpty()` - `true`.
|
||
>
|
||
> **Коллекцию нельзя изменять во время обхода:**
|
||
> ```kotlin
|
||
> 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()`).
|
||
|
||
Дополнительно: [Операции над коллекциями](../../Коллекции/Операции.md)
|
||
Далее: [Строковые шаблоны и вывод](Строковые%20шаблоны%20и%20вывод.md)
|