반환과 점프
반환과 점프 (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) {
// ...
}
이제 break나 continue에 라벨을 붙여서 어디까지 건너뛸지 지정할 수 있어요.
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}
라벨이 붙은 break는 그 라벨이 붙은 반복문 바로 다음 지점으로 점프해요. continue는 그 반복문의 다음 반복으로 진행하죠.
경우에 따라서는 라벨을 명시적으로 붙이지 않고도 break와 continue를 비지역적(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을 반환하라"는 뜻이에요.
어떤 경우에는 라벨을 쓰지 않고도 람다 표현식에서 반환할 수 있어요. 이런 비지역 반환들은 람다 안에 위치하지만, 감싸고 있는 인라인 함수를 빠져나가게 돼요.