반환과 점프

반환과 점프 (Returns and jumps)

코드 흐름을 제어하는 방법은 반복만 있는 게 아니에요. Kotlin에는 구조적인 점프 표현식이 세 가지 있는데, 바로 return, break, continue예요. 이 셋을 이해하면 함수와 반복문을 훨씬 유연하게 다룰 수 있어요.

return은 기본적으로 가장 가까이 있는 함수나 익명 함수에서 값을 돌려주고, break는 가장 가까운 반복문을 끝내며, continue는 가장 가까운 반복문의 다음 단계로 넘어가요.

출처: Kotlin 공식 문서

본문

이 세 표현식은 모두 더 큰 표현식의 일부로도 쓸 수 있어요.

val s = person.name ?: return

이런 표현식들의 타입은 Nothing 타입이에요.

break와 continue 라벨

Kotlin의 모든 표현식에는 라벨을 붙일 수 있어요. 라벨은 식별자 뒤에 @ 기호를 붙인 형태인데, abc@fooBar@처럼 생겼죠. 표현식에 라벨을 붙이려면 앞에 라벨만 적어주면 돼요.

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

이제 breakcontinue에 라벨을 붙여서 어디까지 건너뛸지 지정할 수 있어요.

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

라벨이 붙은 break는 그 라벨이 붙은 반복문 바로 다음 지점으로 점프해요. continue는 그 반복문의 다음 반복으로 진행하죠.

경우에 따라서는 라벨을 명시적으로 붙이지 않고도 breakcontinue를 비지역적(non-local)으로 쓸 수 있어요. 이런 비지역적 사용은 감싸고 있는 인라인 함수 안에서 쓰이는 람다 표현식에서 유효해요.

라벨로 반환하기

Kotlin에서는 함수 리터럴, 지역 함수, 객체 표현식을 이용해 함수를 중첩할 수 있어요. 한정된(qualified) return을 쓰면 바깥쪽 함수에서 반환할 수 있죠.

가장 중요한 용도는 람다 표현식에서 반환하는 거예요. 람다 표현식에서 반환하려면 람다에 라벨을 붙이고 return을 한정하면 돼요.

//sampleStart
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 람다의 호출자(forEach 루프)로의 지역 반환
        print(it)
    }
    print(" done with explicit label")
}
//sampleEnd

fun main() {
    foo()
}

이렇게 하면 람다 표현식에서만 반환돼요. 보통은 암시적 라벨이 더 편한데, 그 이유는 암시적 라벨이 람다가 전달되는 함수와 이름이 같기 때문이에요.

//sampleStart
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // 람다의 호출자(forEach 루프)로의 지역 반환
        print(it)
    }
    print(" done with implicit label")
}
//sampleEnd

fun main() {
    foo()
}

또는 람다 표현식 대신 익명 함수를 쓸 수도 있어요. 익명 함수 안의 return 문은 익명 함수 자신에서 반환돼요.

//sampleStart
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) return  // 익명 함수의 호출자(forEach 루프)로의 지역 반환
        print(value)
    })
    print(" done with anonymous function")
}
//sampleEnd

fun main() {
    foo()
}

앞의 세 예시에서 지역 반환을 쓰는 방식은 일반 반복문에서 continue를 쓰는 것과 비슷하다는 점을 기억해 두세요. break에 직접 해당하는 표현은 없지만, 바깥쪽 run 람다를 하나 추가하고 거기서 비지역적으로 반환하면 흉내 낼 수 있어요.

//sampleStart
fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // run에 전달된 람다로부터의 비지역 반환
            print(it)
        }
    }
    print(" done with nested loop")
}
//sampleEnd

fun main() {
    foo()
}

여기서 비지역 반환이 가능한 이유는 중첩된 forEach() 람다가 인라인 함수로 동작하기 때문이에요.

값을 반환할 때 파서는 한정된 반환을 우선해서 처리해요.

return@a 1

이것은 "라벨이 붙은 식 (@a 1)을 반환하라"가 아니라 "라벨 @a에서 값 1을 반환하라"는 뜻이에요.

어떤 경우에는 라벨을 쓰지 않고도 람다 표현식에서 반환할 수 있어요. 이런 비지역 반환들은 람다 안에 위치하지만, 감싸고 있는 인라인 함수를 빠져나가게 돼요.

더 알아보기 (Learn more)