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

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

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

// обычное использование
var max = a
if (a < b) max = b

// с блоком else
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}

// в виде выражения
val max = if (a > b) a else b

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

val max = if (a > b) {
    print("возвращаем a")
    a
} else {
    print("возвращаем b")
    b
}

Если вы используете if в качестве выражения (например, возвращая его значение или присваивая его переменной), то использование ветки else является обязательным.

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

when определяет условное выражение с несколькими “ветвями”. Оно похоже на оператор switch, присутствующий в C-подобных языках.

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // обратите внимание на блок
        print("x не равен ни 1, ни 2")
    }
}

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

when можно использовать и как выражение, и как оператор. При использовании его в виде выражения значение первой ветки, удовлетворяющей условию, становится значением всего выражения. При использовании в виде оператора значения отдельных веток отбрасываются. В точности как if: каждая ветвь может быть блоком и её значением является значение последнего выражения блока.

Значение ветки else вычисляется в том случае, когда ни одно из условий в других ветках не удовлетворено.

Если when используется как выражение, то ветка else является обязательной, за исключением случаев, в которых компилятор может убедиться, что ветки покрывают все возможные значения. Так происходит, например с записями класса enum и с подтипами sealed (изолированных) классов.

enum class Bit {
  ZERO, ONE
}
val numericValue = when (getRandomBit()) {
    Bit.ZERO -> 0
    Bit.ONE -> 1
    // 'else' не требуется, потому что все случаи учтены
}

В операторах when ветка else является обязательной в следующих условиях:

  • when имеет объект типа Boolean, enum, sealed или их nullable-аналоги;
  • ветки when не охватывают все возможные случаи для этого объекта.
enum class Color {
    RED, GREEN, BLUE
}

when (getColor()) {
    Color.RED -> println("red")
    Color.GREEN -> println("green")
    Color.BLUE -> println("blue")
    // 'else' не требуется, потому что все случаи учтены
}

when (getColor()) {
    Color.RED -> println("red") // нет веток для GREEN и BLUE
    else -> println("not red") // 'else' обязателен
}

Если для нескольких значений выполняется одно и то же действие, то условия можно перечислять в одной ветке через запятую.

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

Помимо констант в ветках можно использовать произвольные выражения.

when (x) {
    s.toInt() -> print("s encodes x")
    else -> print("s does not encode x")
}

Также можно проверять вхождение аргумента в интервал in или !in или его наличие в коллекции:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

Помимо этого Kotlin позволяет с помощью is или !is проверить тип аргумента. Обратите внимание, что благодаря умным приведениям вы можете получить доступ к методам и свойствам типа без дополнительной проверки.

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

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

when {
    x.isOdd() -> print("x is odd")
    y.isEven() -> print("y is even")
    else -> print("x+y is odd")
}

Можно получать переменную внутри when условия по следующему синтаксису:

fun Request.getBody() =
    when (val response = executeRequest()) {
        is Success -> response.body
        is HttpError -> throw HttpException(response.status)
    }

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

Цикл for

Цикл for обеспечивает перебор всех значений, поставляемых итератором. Он эквивалентен циклу foreach в таких языках, как C#.

for (item in collection) print(item)

Телом цикла может быть блок кода.

for (item: Int in ints) {
    // ...
}

Как отмечено выше, цикл for позволяет проходить по всем элементам объекта, имеющего итератор, например:

  • обладающего внутренней или внешней функцией iterator(), возвращаемый тип которой Iterator<>:
    • обладает внутренней или внешней функцией next()
    • обладает внутренней или внешней функцией hasNext(), возвращающей Boolean.

Все три указанные функции должны быть объявлены как operator.

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

for (i in 1..3) {
    println(i)
}
for (i in 6 downTo 0 step 2) {
    println(i)
}

Цикл for по диапазону или массиву компилируется в основанный на индексе цикл, который не создает объект итератора.

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

for (i in array.indices) {
    println(array[i])
}

Также вы можете использовать библиотечную функцию withIndex.

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

Цикл while

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

  • while проверяет условие и, если оно истинно, выполняет тело, а затем возвращается к проверке условия;
  • do-while выполняет тело и только затем проверяет условие. Если оно выполняется, цикл повторяется. Таким образом, тело do-while выполняется по крайней мере один раз независимо от условия.
while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y здесь доступно!

Break и continue в циклах

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