(Kotlin/코틀린) when 표현식 심화 — subject 변수, 조건 결합


1. when 기본 복습

val x = 5
when (x) {
    1    -> println("one")
    2, 3 -> println("two or three")
    else -> println("other")
}

when은 Java의 switch를 대체하지만 훨씬 강력합니다.
표현식으로도 사용 가능하여 값을 반환할 수 있습니다.

val result = when (x) {
    1    -> "one"
    2, 3 -> "two or three"
    else -> "other"
}

2. subject 변수 선언 (Kotlin 1.7+)

when 괄호 안에서 변수를 직접 선언하고, 그 변수는 when 블록 안에서만 유효합니다.

// 기존 방식: 외부에 변수 선언
val result = getResult()
when (result) {
    is Success -> handle(result)
    is Error   -> logError(result)
}
// result는 when 블록 밖에서도 접근 가능

// subject 변수: when 블록 스코프 안에만 존재
when (val result = getResult()) {
    is Success -> handle(result)
    is Error   -> logError(result)
}
// result는 여기서 접근 불가 → 스코프 제한으로 코드 안전성 향상

실전 활용

when (val response = networkCall()) {
    is ApiResponse.Success -> {
        showData(response.data)
        saveToCache(response.data)
    }
    is ApiResponse.Error -> showError(response.message)
    is ApiResponse.Loading -> showLoading()
}
// 정규식 결과를 when 안에서 바로 사용
when (val match = regex.find(input)) {
    null -> println("일치 없음")
    else -> println("일치: ${match.value}")
}

3. 여러 조건 결합 (,)

쉼표로 여러 값을 하나의 브랜치에 묶을 수 있습니다.

enum class Day { MON, TUE, WED, THU, FRI, SAT, SUN }

fun isWeekend(day: Day) = when (day) {
    Day.SAT, Day.SUN -> true
    else             -> false
}

fun getDayType(day: Day) = when (day) {
    Day.MON, Day.TUE, Day.WED, Day.THU, Day.FRI -> "평일"
    Day.SAT, Day.SUN                             -> "주말"
}

4. 범위 체크 (in, !in)

val score = 75

val grade = when (score) {
    in 90..100 -> "A"
    in 80..89  -> "B"
    in 70..79  -> "C"
    in 60..69  -> "D"
    else       -> "F"
}

println(grade)  // C
// !in: 범위 밖
val isOutlier = when (value) {
    !in 0..100 -> true
    else       -> false
}
// 컬렉션 포함 여부
val validCodes = setOf(200, 201, 204)

val message = when (statusCode) {
    in validCodes -> "성공"
    in 400..499   -> "클라이언트 오류"
    in 500..599   -> "서버 오류"
    else          -> "알 수 없음"
}

5. 타입 체크 (is, !is)

fun describe(obj: Any): String = when (obj) {
    is Int    -> "정수: $obj"
    is String -> "문자열 길이: ${obj.length}"  // 스마트 캐스트 — obj가 String으로 자동 캐스팅
    is List<*> -> "리스트 크기: ${obj.size}"
    is Boolean -> if (obj) "참" else "거짓"
    else       -> "기타: ${obj::class.simpleName}"
}

describe(42)        // "정수: 42"
describe("hello")   // "문자열 길이: 5"
describe(true)      // "참"

is 브랜치 안에서는 스마트 캐스트가 적용됩니다. 별도 캐스팅 불필요.


6. sealed class/interface 완전 분기

sealed 계층과 함께 사용하면 else를 생략하고 컴파일러가 누락된 브랜치를 검사합니다.

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Exception) : Result<Nothing>
    data object Loading : Result<Nothing>
}

fun <T> handleResult(result: Result<T>) = when (result) {
    is Result.Success -> processData(result.data)
    is Result.Error   -> showError(result.exception.message)
    is Result.Loading -> showLoading()
    // else 불필요 — 컴파일러가 모든 케이스를 알고 있음
    // 새 서브클래스 추가 시 컴파일 에러로 누락 방지
}

else를 넣으면 새 서브클래스 추가 시 누락되어도 컴파일이 통과합니다.
sealed 계층에서는 else 없이 모든 케이스를 명시하는 것이 안전합니다.


7. subject 없는 when — if-else 대체

when에 인자를 주지 않으면 각 브랜치가 Boolean 조건이 됩니다.

val x = 42

when {
    x < 0    -> println("음수")
    x == 0   -> println("영")
    x < 100  -> println("양수 소수")
    else     -> println("100 이상")
}
// 복잡한 조건도 가독성 있게 표현
fun classify(user: User) = when {
    user.age < 0                          -> throw IllegalArgumentException("나이 오류")
    user.age < 13                         -> "어린이"
    user.age < 18                         -> "청소년"
    user.age < 65 && user.isPremium       -> "프리미엄 성인"
    user.age < 65                         -> "일반 성인"
    else                                  -> "시니어"
}

8. when 표현식으로 값 반환

when을 표현식으로 사용하면 변수에 할당하거나 함수 반환값으로 직접 사용합니다.

// 변수에 할당
val color = when (status) {
    Status.OK      -> Color.GREEN
    Status.WARNING -> Color.YELLOW
    Status.ERROR   -> Color.RED
}

// 함수 반환값으로 직접 사용
fun statusColor(status: Status): Color = when (status) {
    Status.OK      -> Color.GREEN
    Status.WARNING -> Color.YELLOW
    Status.ERROR   -> Color.RED
}

표현식으로 사용할 때는 else 브랜치가 반드시 있어야 합니다 (sealed 계층 제외).


9. Kotlin 2.0 — when의 guard 조건 (when with guards)

Kotlin 2.0에서 추가된 if 조건부 브랜치입니다.

// Kotlin 2.0+
when (value) {
    is String if value.isNotEmpty() -> println("비어있지 않은 문자열: $value")
    is String                       -> println("빈 문자열")
    is Int if value > 0             -> println("양수: $value")
    else                            -> println("기타")
}

기존에는 브랜치 내부에서 다시 if를 써야 했던 것을 브랜치 선언부에서 바로 조건을 추가할 수 있습니다.

// 기존 방식 (Kotlin 1.x)
when (value) {
    is String -> {
        if (value.isNotEmpty()) {
            println("비어있지 않은 문자열")
        } else {
            println("빈 문자열")
        }
    }
}

// 새 방식 (Kotlin 2.0+)
when (value) {
    is String if value.isNotEmpty() -> println("비어있지 않은 문자열")
    is String                       -> println("빈 문자열")
}

10. 정리

기능 문법 주요 활용
기본 분기 value -> 상수/열거형 비교
여러 조건 결합 v1, v2 -> 동일 처리 그룹화
범위 체크 in a..b -> 점수, 상태코드 범위
타입 체크 is Type -> 다형성, 스마트 캐스트
subject 변수 when(val x = expr) 스코프 제한
subject 없는 when when { cond -> } 복잡한 조건 가독성
sealed 완전 분기 else 생략 누락 브랜치 컴파일 검사
guard 조건 is T if cond -> Kotlin 2.0+, 조건부 타입 분기

when을 적극적으로 활용하면 if-else if 체인보다 가독성이 높고, sealed 계층과 조합 시 컴파일러가 분기 누락을 검사해줍니다.



Related Posts