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

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

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

Метки операторов 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, переводит выполнение кода к той его части, которая находится сразу после соответствующей метки loop@. Оператор continue продолжает цикл со следующей его итерации.

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

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

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

Опертор return возвращается из ближайшей функции, в нашем случае foo. (Обратите внимание, что такой местный возврат поддерживается только лямбда-выражениями, переданными инлайн-функциям.) Если нам надо вернуться из лямбда-выражения, к оператору стоит поставить метку и тем самым сделать уточнение для ключевого слова return:

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

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

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)".