Операторы перехода

В Kotlin определено три оператора перехода:

  • return по умолчанию производит возврат из ближайшей окружающей его функции или анонимной функции;
  • break завершает выполнение ближайшего окружающего его цикла;
  • continue продолжает выполнение цикла со следующего его шага, без обработки оставшегося кода текущей итерации.

Все эти выражения можно использовать как часть более крупных выражений:

val s = person.name ?: return

Эти выражения имеют тип Nothing.

Метки операторов break и continue

Любое выражение в Kotlin может быть помечено меткой label. Метки имеют форму идентификатора, за которым следует знак @, например abc@ или fooBar@. Для того чтобы пометить выражение, мы просто ставим метку перед ним.

loop@ for (i in 1..100) {
    // ...
}

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

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop
    }
}

Оператор break, отмеченный @loop, переводит выполнение кода в точку сразу после цикла, отмеченного этой меткой. Оператор continue продолжает цикл со следующей его итерации.

Возврат к меткам

В Kotlin функции могут быть вложены друг в друга с помощью литералов функций, локальных функций и анонимных объектов. Подходящий return позволит вернуться из внешней функции. Одним из самых важных применений этой синтаксической конструкции является возврат из лямбда-выражения. Напомним, что в таких случаях, как в примере ниже, return возвращает из ближайшей заключающей функции - foo:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // нелокальный возврат, непосредственно к объекту вызывающему функцию foo()
        print(it)
    }
    println("эта строка не достижима")
}

Обратите внимание, что такой нелокальный возврат поддерживается только лямбда-выражениями, переданными инлайн-функциям. Чтобы вернуться из лямбда-выражения, к оператору стоит поставить метку и тем самым сделать уточнение для return.

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // локальный возврат внутри лямбды, то есть к циклу forEach
        print(it)
    }
    print(" выполнится с использованием явной метки(lit@)")
}

Теперь он возвращает только из лямбда-выражения. Зачастую намного более удобно использовать неявные метки, потому что такие метки имеют такое же имя, как и функция, к которой относится лямбда.

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // локальный возврат внутри лямбды, то есть к циклу forEach
        print(it)
    }
    print(" выполнится с использованием неявной метки(forEach@)")
}

Возможно также использование анонимной функции в качестве альтернативы лямбда-выражениям. Оператор return возвращает из самой анонимной функции.

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return  // локальный возврат внутри анонимной функции, то есть к циклу forEach
        print(value)
    })
    print(" выполнится с использованием анонимной функции")
}

Обратите внимание, что использование локальных возвратов в предыдущих трех примерах аналогично использованию continue в обычных циклах.

Прямого эквивалента для break не существует, но его можно смоделировать - добавить еще одну вложенную лямбду и нелокально вернуться из нее.

fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // нелокальный возврат из лямбды к вызывающему run
            print(it)
        }
    }
    print(" выполнится с использованием вложенной метки")
}

При возвращении значения парсер отдаёт предпочтение специализированному возврату.

return@a 1

что значит “верни 1 в метке @a”, а не “верни выражение с меткой (@a 1)”.