프로그래밍/Kotlin

[Kotlin] loop에서 break, continue 사용하기

@코린이 2023. 11. 25. 13:18

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