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

160 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)