Условия и циклы

Kotlin предоставляет гибкие инструменты для управления ходом выполнения программы. Используйте if, when и циклы, чтобы описывать условия ясно и выразительно.

Условное выражение if

Чтобы использовать if в Kotlin, укажите проверяемое условие в круглых скобках (), а действие, которое нужно выполнить при истинном результате, — в фигурных скобках {}. Для дополнительных веток и проверок можно использовать else и else if.

Также if можно записывать как выражение. Это позволяет сразу присвоить возвращаемое значение переменной. В такой форме ветка else обязательна. Выражение if выполняет ту же роль, что и тернарный оператор (условие ? значение_если_истина : значение_если_ложь) в других языках.

Например:

fun main() {
    val heightAlice = 160
    val heightBob = 175

    var taller = heightAlice
    if (heightAlice < heightBob) taller = heightBob

    // Использует ветку else
    if (heightAlice > heightBob) {
        taller = heightAlice
    } else {
        taller = heightBob
    }

    // Использует if как выражение
    taller = if (heightAlice > heightBob) heightAlice else heightBob

    // Использует else if как выражение
    val heightLimit = 150
    val heightOrLimit = if (heightLimit > heightAlice) heightLimit else if (heightAlice > heightBob) heightAlice else heightBob

    println("Больший рост: $taller")
    // Больший рост: 175
    println("Рост или ограничение: $heightOrLimit")
    // Рост или ограничение: 175
}

Каждая ветка выражения if может быть блоком. В этом случае результатом становится значение последнего выражения в блоке:

fun main() {
    val heightAlice = 160
    val heightBob = 175

    val taller = if (heightAlice > heightBob) {
        print("Выбираем Alice\n")
        heightAlice
    } else {
        print("Выбираем Bob\n")
        heightBob
    }

    println("Больший рост: $taller")
}

Выражения и операторы when

when — это условная конструкция, которая выполняет код в зависимости от нескольких возможных значений или условий. Она похожа на оператор switch в Java, C и других языках. when вычисляет свой аргумент и последовательно сравнивает результат с каждой веткой, пока не будет выполнено одно из условий. Например:

fun main() {
    val userRole = "Editor"

    when (userRole) {
        "Viewer" -> print("Пользователь может только читать")
        "Editor" -> print("Пользователь может редактировать содержимое")
        else -> print("Роль пользователя не распознана")
    }
    // Пользователь может редактировать содержимое
}

when можно использовать как выражение или как оператор. В виде выражения when возвращает значение, которое можно использовать дальше в коде. В виде оператора when выполняет действие и не возвращает результат:

Выражение:

// Возвращает строку, присвоенную
// переменной text
val text = when (x) {
    1 -> "x == 1"
    2 -> "x == 2"
    else -> "x не равен ни 1, ни 2"
}

Оператор:

// Не возвращает результат, а вызывает
// функцию печати
when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> print("x не равен ни 1, ни 2")
}

Кроме того, when можно использовать с объектом проверки или без него. Поведение остаётся тем же, но объект проверки обычно делает код понятнее и проще в сопровождении, потому что явно показывает, что именно проверяется.

С объектом проверки x:

when (x) { ... }

Без объекта проверки:

when { ... }

От способа использования when зависит, нужно ли покрывать ветками все возможные случаи. Такое полное покрытие называется исчерпывающим.

Операторы

Если when используется как оператор, покрывать все возможные случаи не нужно. В следующем примере некоторые случаи не покрыты, поэтому ни одна ветка не выполняется, но ошибки при этом не возникает:

fun main() {
    val deliveryStatus = "OutForDelivery"

    when (deliveryStatus) {
        // Покрыты не все случаи
        "Pending" -> print("Ваш заказ готовится")
        "Shipped" -> print("Ваш заказ уже в пути")
    }
}

Как и в if, каждая ветка может быть блоком, а её значением является значение последнего выражения в этом блоке.

Выражения

Если when используется как выражение, необходимо покрыть все возможные случаи. Значение первой подходящей ветки становится значением всего выражения. Если покрыты не все случаи, компилятор выдаёт ошибку.

Если у выражения when есть объект проверки, можно использовать ветку else, чтобы гарантировать покрытие всех возможных случаев, но это не обязательно. Например, если объект проверки имеет тип Boolean, тип класса enum, тип sealed-класса или один из их nullable-аналогов, можно покрыть все случаи без ветки else:

import kotlin.random.Random

enum class Bit {
    ZERO, ONE
}

fun getRandomBit(): Bit {
    return if (Random.nextBoolean()) Bit.ONE else Bit.ZERO
}

fun main() {
    val numericValue = when (getRandomBit()) {
        // Ветка else не нужна, потому что покрыты все случаи
        Bit.ZERO -> 0
        Bit.ONE -> 1
    }

    println("Случайный бит как число: $numericValue")
    // Случайный бит как число: 0
}

Совет. Чтобы упростить выражения when и сократить повторения, попробуйте контекстно-зависимое разрешение (сейчас доступно в предварительном режиме). Эта возможность позволяет опускать имя типа при использовании записей enum или членов sealed-класса в выражениях when, если ожидаемый тип известен.

Подробнее см. в разделе Preview of context-sensitive resolution и в соответствующем KEEP-предложении.

Если у выражения when нет объекта проверки, ветка else обязательна, иначе компилятор выдаст ошибку. Ветка else вычисляется, когда ни одно из условий других веток не выполнено:

fun main() {
    val localFileSize = 1200
    val remoteFileSize = 1200

    val message = when {
        localFileSize > remoteFileSize -> "Локальный файл больше удалённого"
        localFileSize < remoteFileSize -> "Локальный файл меньше удалённого"
        else -> "Локальный и удалённый файлы имеют одинаковый размер"
    }

    println(message)
    // Локальный и удалённый файлы имеют одинаковый размер
}

Другие способы использования when

Выражения и операторы when предлагают разные способы упростить код, обработать несколько условий и выполнить проверки типов.

Объединяйте несколько условий в одной ветке с помощью запятых:

fun main() {
    val ticketPriority = "High"

    when (ticketPriority) {
        "Low", "Medium" -> print("Стандартное время ответа")
        else -> print("Высокий приоритет обработки")
    }
}

Используйте в условиях веток выражения, которые вычисляются в true или false:

fun main() {
    val storedPin = "1234"
    val enteredPin = 1234

    when (enteredPin) {
        // Выражение
        storedPin.toInt() -> print("PIN-код верный")
        else -> print("Неверный PIN-код")
    }
}

Проверяйте, входит ли значение в диапазон или коллекцию, с помощью ключевых слов in и !in:

fun main() {
    val x = 7
    val validNumbers = setOf(15, 16, 17)

    when (x) {
        in 1..10 -> print("x входит в диапазон")
        in validNumbers -> print("x является допустимым")
        !in 10..20 -> print("x находится вне диапазона")
        else -> print("ни одно из условий выше не выполнено")
    }
}

Проверяйте тип значения с помощью ключевых слов is и !is. Благодаря умным приведениям можно обращаться к функциям и свойствам этого типа напрямую:

fun hasPrefix(input: Any): Boolean = when (input) {
    is String -> input.startsWith("ID-")
    else -> false
}

fun main() {
    val testInput = "ID-98345"
    println(hasPrefix(testInput))
    // true
}

Используйте when вместо традиционной цепочки if-else if. Без объекта проверки условия веток являются простыми логическими выражениями. Выполняется первая ветка, условие которой равно true:

fun Int.isOdd() = this % 2 != 0
fun Int.isEven() = this % 2 == 0

fun main() {
    val x = 5
    val y = 8

    when {
        x.isOdd() -> print("x нечётный")
        y.isEven() -> print("y чётный")
        else -> print("x + y нечётно")
    }
    // x нечётный
}

Наконец, объект проверки можно захватить в переменную с помощью следующего синтаксиса:

fun main() {
    val message = when (val input = "yes") {
        "yes" -> "Вы сказали yes"
        "no" -> "Вы сказали no"
        else -> "Нераспознанный ввод: $input"
    }

    println(message)
    // Вы сказали yes
}

Область видимости переменной, объявленной как объект проверки, ограничена телом выражения или оператора when.

Guard conditions в when {id=“guard-conditions-in-when-expressions”}

Guard conditions позволяют включать в ветки выражения или оператора when более одного условия, делая сложный поток управления более явным и кратким. Их можно использовать с when, если у него есть объект проверки.

Размещайте guard condition после основного условия в той же ветке, отделяя его с помощью if:

sealed interface Animal {
    data class Cat(val mouseHunter: Boolean) : Animal
    data class Dog(val breed: String) : Animal
}

fun feedDog() = println("Кормим собаку")
fun feedCat() = println("Кормим кошку")

fun feedAnimal(animal: Animal) {
    when (animal) {
        // Ветка только с основным условием
        // Вызывает feedDog(), если animal — Dog
        is Animal.Dog -> feedDog()
        // Ветка с основным и дополнительным условием
        // Вызывает feedCat(), если animal — Cat и не ловит мышей
        is Animal.Cat if !animal.mouseHunter -> feedCat()
        // Печатает "Неизвестное животное", если ни одно из условий выше не подошло
        else -> println("Неизвестное животное")
    }
}

fun main() {
    val animals = listOf(
        Animal.Dog("Beagle"),
        Animal.Cat(mouseHunter = false),
        Animal.Cat(mouseHunter = true)
    )

    animals.forEach { feedAnimal(it) }
    // Кормим собаку
    // Кормим кошку
    // Неизвестное животное
}

Guard conditions нельзя использовать, если в ветке несколько условий, разделённых запятой. Например:

0, 1 -> print("x == 0 или x == 1")

В одном выражении или операторе when можно сочетать ветки с guard conditions и без них. Код в ветке с guard condition выполняется только в том случае, если и основное условие, и guard condition вычисляются в true. Если основное условие не подходит, guard condition не вычисляется.

Так как операторы when не обязаны покрывать все случаи, использование guard conditions в операторах when без ветки else означает, что при отсутствии подходящих условий код не выполнится.

В отличие от операторов, выражения when должны покрывать все случаи. Если вы используете guard conditions в выражениях when без ветки else, компилятор потребует обработать все возможные случаи, чтобы избежать ошибок во время выполнения.

Объединяйте несколько guard conditions в одной ветке с помощью логических операторов && (AND) или || (OR). Используйте скобки вокруг логических выражений, чтобы избежать путаницы:

when (animal) {
    is Animal.Cat if (!animal.mouseHunter && animal.hungry) -> feedCat()
}

Guard conditions также поддерживают else if:

when (animal) {
    // Проверяет, является ли animal собакой
    is Animal.Dog -> feedDog()
    // Guard condition, который проверяет, является ли animal кошкой, не ловящей мышей
    is Animal.Cat if !animal.mouseHunter -> feedCat()
    // Вызывает giveLettuce(), если ни одно из условий выше не подошло и animal.eatsPlants равно true
    else if animal.eatsPlants -> giveLettuce()
    // Печатает "Неизвестное животное", если ни одно из условий выше не подошло
    else -> println("Неизвестное животное")
}

Цикл for

Используйте цикл for, чтобы проходить по коллекции, массиву или диапазону:

for (item in collection) print(item)

Тело цикла for может быть блоком в фигурных скобках {}.

fun main() {
    val shoppingList = listOf("Молоко", "Бананы", "Хлеб")

    println("Купить:")
    for (item in shoppingList) {
        println("- $item")
    }
    // Купить:
    // - Молоко
    // - Бананы
    // - Хлеб
}

Диапазоны

Чтобы перебрать диапазон чисел, используйте выражение диапазона с операторами .. и ..<:

fun main() {
    println("Закрытый диапазон:")
    for (i in 1..6) {
        print(i)
    }
    // Закрытый диапазон:
    // 123456

    println("\nОткрытый диапазон:")
    for (i in 1..<6) {
        print(i)
    }
    // Открытый диапазон:
    // 12345

    println("\nОбратный порядок с шагом 2:")
    for (i in 6 downTo 0 step 2) {
        print(i)
    }
    // Обратный порядок с шагом 2:
    // 6420
}

Массивы

Если нужно пройти по массиву или списку с индексом, используйте свойство indices:

fun main() {
    val routineSteps = arrayOf("Проснуться", "Почистить зубы", "Сварить кофе")

    for (i in routineSteps.indices) {
        println(routineSteps[i])
    }
    // Проснуться
    // Почистить зубы
    // Сварить кофе
}

Также можно использовать функцию .withIndex() из стандартной библиотеки:

fun main() {
    val routineSteps = arrayOf("Проснуться", "Почистить зубы", "Сварить кофе")

    for ((index, value) in routineSteps.withIndex()) {
        println("Шаг с индексом $index: \"$value\"")
    }
    // Шаг с индексом 0: "Проснуться"
    // Шаг с индексом 1: "Почистить зубы"
    // Шаг с индексом 2: "Сварить кофе"
}

Итераторы

Цикл for перебирает всё, что предоставляет итератор. Коллекции предоставляют итераторы по умолчанию, а диапазоны и массивы компилируются в циклы, основанные на индексах.

Можно создавать собственные итераторы, предоставляя функцию-член или функцию-расширение iterator(), которая возвращает Iterator<>. У итератора, который возвращает iterator(), должны быть функция next() и функция hasNext(), возвращающая Boolean.

Самый простой способ создать собственный итератор для класса — унаследоваться от интерфейса Iterable<T> и переопределить уже имеющиеся функции iterator(), next() и hasNext(). Например:

class Booklet(val totalPages: Int) : Iterable<Int> {
    override fun iterator(): Iterator<Int> {
        return object : Iterator<Int> {
            var current = 1

            override fun hasNext() = current <= totalPages
            override fun next() = current++
        }
    }
}

fun main() {
    val booklet = Booklet(3)
    for (page in booklet) {
        println("Читаем страницу $page")
    }
    // Читаем страницу 1
    // Читаем страницу 2
    // Читаем страницу 3
}

Подробнее см. в разделах Интерфейсы и Наследование.

Также можно создать функции с нуля. В этом случае добавьте к функциям ключевое слово operator:

class Booklet(val totalPages: Int) {
    operator fun iterator(): Iterator<Int> {
        return object {
            var current = 1

            operator fun hasNext() = current <= totalPages
            operator fun next() = current++
        }.let {
            object : Iterator<Int> {
                override fun hasNext() = it.hasNext()
                override fun next() = it.next()
            }
        }
    }
}

fun main() {
    val booklet = Booklet(3)
    for (page in booklet) {
        println("Читаем страницу $page")
    }
    // Читаем страницу 1
    // Читаем страницу 2
    // Читаем страницу 3
}

Цикл while

Циклы while и do-while непрерывно выполняют код в своём теле, пока условие истинно. Разница между ними заключается во времени проверки условия:

  • while проверяет условие и, если оно истинно, выполняет код в своём теле, а затем возвращается к проверке условия;
  • do-while выполняет код в своём теле и только затем проверяет условие. Если оно истинно, цикл повторяется. Поэтому тело do-while выполняется как минимум один раз независимо от условия.

Для цикла while поместите проверяемое условие в круглые скобки (), а тело — в фигурные скобки {}:

fun main() {
    var carsInGarage = 0
    val maxCapacity = 3

    while (carsInGarage < maxCapacity) {
        println("Машина заехала. Машин в гараже: ${++carsInGarage}")
    }
    // Машина заехала. Машин в гараже: 1
    // Машина заехала. Машин в гараже: 2
    // Машина заехала. Машин в гараже: 3

    println("Гараж заполнен!")
    // Гараж заполнен!
}

Для цикла do-while сначала поместите тело в фигурные скобки {}, а затем укажите проверяемое условие в круглых скобках ():

import kotlin.random.Random

fun main() {
    var roll: Int

    do {
        roll = Random.nextInt(1, 7)
        println("Выпало $roll")
    } while (roll != 6)
    // Выпало 2
    // Выпало 6

    println("Выпала 6! Игра окончена.")
    // Выпала 6! Игра окончена.
}

Break и continue в циклах

Kotlin поддерживает привычные операторы break и continue в циклах. См. Операторы перехода.