[Kotlin] loop에서 break, continue 사용하기
loop문에서 continue와 break를 사용해야 했다. Java에서 작성하던 방식과 비슷하게 작성햇는데 내가 생각한 대로 동작이 일어나지 않아 레퍼런스 문서를 찾게 되었고, 잘못 사용했다는 것을 깨달아 블로그에 정리한다.
1. break / continue
우리가 흔히 사용하는 break, continue는 for문 안에서 사용할 수 있다.
fun main() {
for (i in 1..5) {
if (i==3) break
print("$i ")
}
}
// 결과
1 2
문제는 Iterable<T>.forEach() 에서는 break, continue를 사용할 수 없다는 점이다. 아래와 같이 작성하면 break 때문에 compile이 불가능하다.
fun main() {
(1..5).forEach { i ->
if (i==3) break
print("$it ")
}
}
'break' and 'continue' are only allowed inside a loop
collection.forEach()는 for를 Iterable로 한번 감싼 형태이기 때문에 loop라고 인식을 하지 않는 듯하다.
나는 collection을 chaining하던 중이었기 때문에 위의 for()문은 사용할 수 없었고, 다른 방법을 찾아 보았다.
2. Label 사용
fun main() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return
print("$it ")
}
println("다른 로직 처리")
}
// 결과
1 2
break 자리에 return을 사용하면 될줄 알았는데 아예 함수가 끝나버린다..ㅎㅎ
2-1. label을 사용한 continue
fun main() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach
print("$it ")
}
println("다른 로직 처리")
}
// 결과
1 2 4 5 다른 로직 처리
return이지만 현재의 loop를 태깅하면 continue 동작이 실행된다...
2-2. label을 사용한 break
fun main() {
run {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@run
print("$it ")
}
}
println("다른 로직 처리")
}
// 결과
1 2 다른 로직 처리
그리고 만약 코드에 run이나 forEach가 많다면 태그를 붙여 확실하게 return 하는 expression을 지정할 수도 있다.
fun main() {
run startForeach@ { // 이름 태깅
listOf(1, 2, 3, 4, 5).forEach loop@ {
if (it == 3) return@startForeach // 태깅한 이름을 지정
print("$it ")
}
}
println("다른 로직 처리")
}
이제서야 내가 원하는 break 방식을 찾았다. 그렇지만 불필요한 run을 한번 더 감싸는 방식이 마음에 들지 않았다.
다른 방식을 찾아보기로 했다.
3. Collection 함수 사용
3-1. filter()를 사용한 continue
만약 중간에 continue가 들어가야 한다면 사용할 수 없는 방법이지만, 처음부터 조건문을 사용하여 continue 문을 거는 경우라면 filter()를 사용해볼 수 있다. 조건에 따라 filterNot()을 사용할 수도 있다.
fun main() {
run startForeach@ {
listOf(1, 2, 3, 4, 5)
.filter { it != 3 }
// .filterNot { it == 3 }
.forEach {
print("$it ")
}
}
println("다른 로직 처리")
}
// 결과
1 2 4 5 다른 로직 처리
3-2. takeWhile()을 사용한 break
위와 마찬가지로 처음부터 조건문을 사용하여 break를 거는 경우라면 takeWhile()을 사용할 수 있다. takeWhile()은 자주 쓰지 않는 함수라 찾아보았다.
public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
val list = ArrayList<T>()
for (item in this) {
if (!predicate(item))
break
list.add(item)
}
return list
}
조건문이 실행되기 전까지의 list를 반환하는 함수이다. filter와 다른 점은 filter는 전체 중 조건문에 해당하는 전체 리스트 반환이지만, takeWhile()은 조건문에 해당하지 않는 순간 loop가 종료되고, 그 순간까지의 리스트만 반환한다.
fun main() {
run startForeach@ {
listOf(1, 2, 3, 4, 5)
.takeWhile { it != 3 }
.forEach {
print("$it ")
}
}
println("다른 로직 처리")
}
// 결과
1 2 다른 로직 처리
결론
나는 개인적으로 2-2의 방법은 불필요한 run()을 작성하는게 별로라고 생각되어 3-2의 takeWhile()문을 사용하여 해결했다. 그러나 로직을 실행하다 break를 해야하는 순간에는 2-2를 선택할 수 밖에 없고, 어떻게 보면 그게 더 직관적인 코드일 수도 있다. 이것은 개인이 취향이기 때문에 여러가지 방법을 알아두고 사용하는 것이 좋겠다.
참고1: https://kotlinlang.org/docs/returns.html#break-and-continue-labels
참고2: https://www.baeldung.com/kotlin/break-continue-functional-loop