제어문과 표현식 — when과 if가 값을 반환하는 언어
코틀린에서 if와 when은 단순한 제어문이 아니라 "표현식(expression)"이다. 값을 반환할 수 있다는 뜻이다. 자바 개발자에게는 익숙하지 않은 개념이지만, 코틀린스러운 코드를 작성하려면 반드시 알아야 한다. 면접에서도 when의 활용법은 자주 등장하니 꼼꼼하게 정리해보자.
if는 표현식이다 — 삼항 연산자가 없는 이유
코틀린에는 삼항 연산자(? :)가 없다. if 자체가 값을 반환하기 때문이다.
// 자바 스타일
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 코틀린 스타일 — if가 값을 반환
val max = if (a > b) a else b
표현식으로 쓸 때의 규칙
if를 표현식으로 사용하면 else가 필수다. 컴파일러가 모든 경우에 값을 반환하도록 보장해야 하기 때문이다.
// val result = if (x > 0) "양수" // 컴파일 에러! else가 필요
val result = if (x > 0) "양수" else "0 이하" // OK
// 블록으로도 사용 가능 — 마지막 줄이 반환값
val description = if (score >= 90) {
println("축하합니다")
"A등급" // 이 줄이 반환값
} else if (score >= 80) {
"B등급"
} else {
"C등급"
}
when — 자바 switch의 진화형
when은 자바의 switch를 완전히 대체하면서도 훨씬 강력하다.
fun describe(x: Any): String = when (x) {
1 -> "하나"
"hello" -> "인사"
is Long -> "Long 타입"
!is String -> "문자열이 아님"
else -> "기타"
}
when도 표현식이다
val message = when (statusCode) {
200 -> "성공"
404 -> "찾을 수 없음"
500 -> "서버 에러"
else -> "알 수 없는 상태" // 표현식일 때 else 필수
}
when의 다양한 분기 패턴
when이 강력한 이유는 다양한 조건을 표현할 수 있기 때문이다.
fun classify(value: Any) {
when (value) {
// 1. 상수 매칭
0 -> println("영")
1, 2 -> println("1 또는 2") // 여러 값을 콤마로 묶기
// 2. 범위 검사
in 3..10 -> println("3에서 10 사이")
!in 100..200 -> println("100~200 밖")
// 3. 타입 검사 (스마트 캐스트 적용)
is String -> println("문자열 길이: ${value.length}")
is List<*> -> println("리스트 크기: ${value.size}")
// 4. else
else -> println("기타")
}
}
인자 없는 when — if-else 체인 대체
fun classifyTemperature(temp: Int): String = when {
temp < 0 -> "영하"
temp < 10 -> "춥다"
temp < 25 -> "적당하다"
temp < 35 -> "덥다"
else -> "폭염"
}
인자 없는 when은 각 분기에 Boolean 조건을 넣을 수 있어서, 복잡한 if-else if 체인을 깔끔하게 정리할 수 있다.
when과 sealed class — 궁합이 좋다
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
fun handle(result: Result): String = when (result) {
is Result.Success -> "데이터: ${result.data}"
is Result.Error -> "에러: ${result.message}"
Result.Loading -> "로딩 중..."
// else 불필요! sealed class의 모든 하위 타입을 처리했으므로
}
sealed class와 when을 함께 쓰면 else가 필요 없다. 새로운 하위 타입을 추가하면 컴파일러가 처리하지 않은 분기를 알려준다. 실수를 방지하는 강력한 조합이다.
Range와 Progression — 범위를 다루는 방법
// 닫힌 범위 (1부터 10까지, 10 포함)
val closed = 1..10 // 1, 2, 3, ..., 10
// 반열린 범위 (1부터 10 미만)
val halfOpen = 1..<10 // 1, 2, 3, ..., 9 (Kotlin 1.8+)
// 역순 범위
val reversed = 10 downTo 1 // 10, 9, 8, ..., 1
// step으로 간격 지정
val stepped = 1..20 step 3 // 1, 4, 7, 10, 13, 16, 19
Range 활용 예시
// in 연산자로 범위 검사
val age = 25
if (age in 19..64) {
println("성인")
}
// for 루프에서 사용
for (i in 1..5) {
print("$i ") // 1 2 3 4 5
}
// 인덱스와 함께
val items = listOf("사과", "바나나", "체리")
for (i in items.indices) {
println("$i: ${items[i]}")
}
// withIndex 활용
for ((index, value) in items.withIndex()) {
println("$index: $value")
}
Char와 String에서도 Range 사용 가능
val isLowerCase = 'a' in 'a'..'z'
val isUpperCase = 'A' in 'A'..'Z'
// when에서도 활용
fun classifyChar(c: Char) = when (c) {
in 'a'..'z' -> "소문자"
in 'A'..'Z' -> "대문자"
in '0'..'9' -> "숫자"
else -> "기타"
}
레이블(@label) — break, continue, return 제어
코틀린에서는 중첩 루프나 람다에서 흐름을 세밀하게 제어할 수 있다.
레이블이 붙은 break/continue
// 레이블로 바깥 루프를 제어
outer@ for (i in 1..5) {
for (j in 1..5) {
if (j == 3) break@outer // 바깥 루프까지 탈출
println("$i, $j")
}
}
// 출력: 1, 1 / 1, 2 (j가 3이 되면 전체 루프 종료)
outer@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) continue@outer // 바깥 루프의 다음 반복으로
println("$i, $j")
}
}
// 출력: 1, 1 / 2, 1 / 3, 1
람다에서의 return
이 부분이 면접에서 자주 나온다. 람다 안에서 return을 쓰면 바깥 함수까지 종료된다.
fun findFirstNegative(numbers: List<Int>): Int? {
numbers.forEach {
if (it < 0) return it // findFirstNegative 함수 전체를 반환!
}
return null
}
람다만 종료하고 싶다면 레이블을 사용한다.
fun printPositives(numbers: List<Int>) {
numbers.forEach {
if (it < 0) return@forEach // 이 람다만 종료 (continue처럼 동작)
println(it)
}
println("완료") // 이 줄은 항상 실행됨
}
익명 함수에서의 return
fun printPositives2(numbers: List<Int>) {
numbers.forEach(fun(value) {
if (value < 0) return // 익명 함수만 종료 (람다와 다르게 동작)
println(value)
})
println("완료") // 항상 실행됨
}
정리하면 이렇다.
| 구문 | 동작 |
|---|---|
return (람다 내부) | 바깥 함수까지 종료 |
return@label | 해당 람다만 종료 |
return (익명 함수 내부) | 익명 함수만 종료 |
for 루프 — 자바의 for-each만 있다
코틀린에는 C 스타일 for 루프(for (int i = 0; i < 10; i++))가 없다.
// Range 기반 반복
for (i in 0 until 10) { // 0..<10과 동일
print("$i ")
}
// 컬렉션 순회
val fruits = listOf("사과", "바나나", "체리")
for (fruit in fruits) {
println(fruit)
}
// 구조 분해
val map = mapOf("이름" to "코틀린", "버전" to "2.0")
for ((key, value) in map) {
println("$key: $value")
}
while과 do-while — 자바와 동일
var count = 0
while (count < 5) {
println(count)
count++
}
do {
val input = readLine()
} while (input != "quit")
while과 do-while은 자바와 완전히 같다. 특별한 건 없지만, 코틀린에서는 가능하면 for-in이나 컬렉션 함수를 선호한다.
정리 — 면접에서 기억할 포인트
- if는 표현식 — 값을 반환하므로 삼항 연산자가 필요 없다
- when은 switch의 상위호환 — 타입 검사, 범위, 여러 값 매칭 가능
- sealed class + when — else 없이 모든 분기를 컴파일 타임에 보장
- Range —
..,..<,downTo,step으로 범위 표현 - 레이블 return — 람다 안의 return은 바깥 함수를 종료.
return@label로 람다만 종료 가능