코틀린 베이직 KB 09 when — break 없는 switch
코틀린 베이직 · 9/26

KB 09 when — break 없는 switch

gabury1고친 사람 github-actions[bot]

0. break 를 빼먹은 switch, break 가 없는 when

자바의 옛 switch 에서 break 를 빼먹어 본 적이 있다면 결과를 압니다. 코틀린의 when 에는 그 break 를 쓸 곳이 아예 없습니다. 같은 모양으로 둘을 맞대 봤습니다.

Kotlin
fun main() {
    val x = 1
    when (x) {
        1 -> println("one")      // one
        2 -> println("two")
        else -> println("other")
    }
}
  • fun main() — 프로그램이 시작하는 함수입니다. 클래스 없이 파일 바로 아래에 둡니다
  • val x = 1 — 다시 대입할 수 없는 변수입니다. 타입을 안 적으면 값에서 Int 를 알아냅니다
  • when (x) { ... } — 괄호 안 값을 아래 줄들과 위에서부터 차례로 견줘, 처음 맞는 줄 하나를 실행합니다
  • 1 -> ... — 이런 줄 하나를 분기라고 부릅니다. 화살표 왼쪽이 분기 조건, 오른쪽이 실행할 코드입니다. else 는 자바의 default 에 해당합니다

First.kt 로 저장하고 코틀린 컴파일러 kotlinc 로 컴파일해 돌렸습니다.

터미널
$ kotlinc First.kt -d out
$ kotlin -cp out FirstKt
  • -d out — 결과물을 out 폴더에 쓰라는 옵션입니다
  • -cp out — 클래스를 찾을 폴더(classpath)를 out 으로 정합니다
  • kotlin — 코틀린 설치에 딸린 실행 명령입니다. java 와 같은 일을 하되, 코틀린 표준 라이브러리를 classpath 에 스스로 넣습니다
  • FirstKt — 컴파일러가 파일 이름 First 에 Kt 를 붙여 지은 클래스입니다. 파일 바로 아래 쓴 함수는 이 클래스의 static 메서드가 됩니다

같은 모양의 자바 switch 입니다. case 마다 break 를 쓰지 않고 jout 폴더에 컴파일해 돌렸습니다.

Java
public class First {
    public static void main(String[] args) {
        int x = 1;
        switch (x) {
            case 1:
                System.out.println("one");
            case 2:
                System.out.println("two");
            default:
                System.out.println("other");
        }
    }
}
터미널
$ javac -d jout First.java
$ java -cp jout First
one
two
other

자바는 세 줄, 코틀린은 한 줄입니다. 자바의 case 1: 은 들어가는 입구일 뿐이라, break 가 없으면 아래 case 의 코드까지 이어서 실행합니다. 이 동작을 fall-through 라고 부릅니다. 코틀린의 when 은 맞은 분기 하나만 실행하고 끝납니다. 다음 분기로 넘어가는 문법이 없으니 fall-through 도 없습니다.

자바도 JDK 14 에서 화살표 모양의 case 1 -> 를 정식 문법으로 들였습니다. 이 모양 역시 fall-through 가 없습니다(JEP 361. JEP 는 JDK 에 들어갈 기능을 적은 제안 문서입니다). 겉모습만 보면 when 은 이 화살표 switch 와 닮았습니다. 둘은 화살표 왼쪽에 무엇을 쓸 수 있느냐에서 갈리는데, 1절부터 4절까지가 그 이야기입니다.

이 편의 출력은 전부 Kotlin 2.4.10, JDK 21 에서 직접 돌린 결과입니다.

when 안의 break 는 반복문을 빠져나간다

그럼 자바 버릇대로 when 안에 break 를 쓰면 어떻게 될까요. 컴파일 에러가 아니라 다른 동작이 나옵니다.

Kotlin
fun main() {
    for (i in 1..3) {
        when (i) {
            2 -> break
            else -> println(i)  // 1
        }
    }
    println("end")              // end
}
  • for (i in 1..3) — i 를 1, 2, 3 으로 바꿔 가며 도는 반복입니다. 1..3 은 1 이상 3 이하의 범위입니다. 범위 문법은 KB 10 범위와 반복 편에서 자세히 봅니다
  • 2 -> break — i 가 2 일 때 break 를 실행합니다

코틀린 쪽을 돌렸습니다.

터미널
$ kotlinc Loop.kt -d out
$ kotlin -cp out LoopKt

같은 일을 하는 자바 Loop.java 는 for (int i = 1; i <= 3; i++) 안에 이런 switch 를 두었고, 컴파일해 돌리니 세 줄이 나왔습니다.

Java
switch (i) {
    case 2: break;
    default: System.out.println(i);
}
터미널
$ javac -d jout Loop.java
$ java -cp jout Loop
1
3
end

코틀린은 3 을 찍지 않았습니다. 자바의 break 는 가장 가까운 switch 를 빠져나가므로 반복이 계속됩니다. 코틀린의 when 은 break 로 빠져나가는 대상이 아닙니다. 그래서 break 가 바깥 for 를 끝냅니다(코틀린 1.4 부터 이렇게 동작합니다. 그 전에는 컴파일 에러였습니다).

1. 값 매칭 — 여러 값, 식, else

when 을 가장 흔히 쓰는 모양은 괄호 안 값을 후보 여럿과 견주는 것입니다. 이 절에서는 분기 조건에 값을 몇 개, 어떤 모양으로 쓸 수 있는지를 한 함수로 봅니다.

Kotlin
fun describe(x: Int, limit: Int) {
    when (x) {
        0 -> println("zero")
        1, 2 -> println("small")
        limit -> println("limit")
        limit * 2 -> println("double")
        else -> println("other")
    }
}

fun main() {
    describe(2, 10)   // small
    describe(10, 10)  // limit
    describe(20, 10)  // double
    describe(7, 10)   // other
}
  • x: Int — 코틀린은 이름 뒤에 콜론, 그 뒤에 타입을 씁니다. 자바의 int x 입니다
  • 1, 2 -> — 쉼표로 늘어놓은 값 중 하나라도 같으면 이 분기를 실행합니다
  • limit -> — 상수가 아니라 매개변수입니다. x 가 limit 과 같으면 맞습니다
  • limit * 2 -> — 계산식도 분기 조건이 됩니다
터미널
$ kotlinc Values.kt -d out
$ kotlin -cp out ValuesKt

2 는 1, 2 분기에, 10 은 limit 분기에, 20 은 limit * 2 분기에 맞았습니다. 어디에도 안 맞은 7 은 else 로 갔습니다.

같은 함수를 JDK 21 의 화살표 switch 로 옮긴 Values.java 입니다. case 1, 2 -> 줄에서는 에러가 없었고, case limit -> 줄에서 멈췄습니다. limit * 2 도 상수가 아니라서 자바 쪽에는 처음부터 넣지 않았습니다.

터미널
$ javac -d jout Values.java
Values.java:6: error: constant expression required
            case limit -> System.out.println("limit");
                 ^
1 error

constant expression required 는 이 case 에 컴파일 시점 상수가 와야 한다는 뜻입니다. 컴파일 시점 상수는 컴파일할 때 이미 값이 정해져 있는 것, 곧 1 같은 리터럴이나 final 상수입니다. limit 은 부를 때마다 값이 달라지는 매개변수라 여기 올 수 없습니다. 자바의 case 는 상수만 받습니다.

코틀린은 견주는 방식부터 다릅니다. 괄호 안 값이 분기 조건 식의 값과 같은지를 위에서부터 차례로 비교할 뿐입니다. 그래서 괄호 안 값과 비교할 수 있는 타입이면 어떤 식이든 분기 조건이 됩니다.

다만 타입은 맞아야 합니다. Int 값에 "a" -> 를 쓴 IntStr.kt 는 error: incompatible types 'Int' and 'String'. 로 멈췄습니다.

같은 값이 두 번 나오면 코틀린은 경고만 냅니다. 1 -> 를 두 번 쓴 Dup.kt 에서 warning: duplicate branch condition in 'when'. 이 나왔습니다. 실행하면 첫 분기의 first 만 찍었습니다. 자바는 같은 모양을 error: duplicate case label 로 거절했습니다.

when 의 분기 조건은 상수가 아니어도 됩니다. 매개변수든 계산식이든, 괄호 안 값과 견줄 수만 있으면 놓을 수 있습니다.

2. 인자 없는 when — if-else if 사슬 대신

1절의 when 은 값 하나를 여러 후보와 견줬습니다. 그런데 조건마다 보는 변수가 다르면 견줄 값 하나가 없습니다. 이럴 때 코틀린은 괄호를 떼고 조건만 늘어놓습니다. 같은 조건을 자바의 if-else if 사슬로 옮긴 Grade.java 와 함께 돌렸습니다.

Kotlin
fun main() {
    val score = 85
    val name = ""
    when {
        score >= 90 -> println("A")
        // no name
        name.isEmpty() -> println("no name")
        score >= 80 -> println("B")
        else -> println("C")
    }
}
터미널
$ kotlinc Grade.kt -d out
$ kotlin -cp out GradeKt
$ javac -d jout Grade.java
$ java -cp jout Grade
  • when { — 괄호 안 값이 없는 모양입니다. 분기 조건마다 참이나 거짓이 나오는 식을 씁니다
  • name.isEmpty() — 빈 문자열이면 참입니다. 앞뒤 분기와 달리 score 가 아니라 name 을 봅니다

코틀린과 자바 모두 no name 한 줄을 찍었습니다. 둘 다 B 가 아니라 no name 입니다. score >= 80 도 참이지만, 그보다 위의 name.isEmpty() 가 먼저 참이 되어 거기서 끝났습니다. 인자 없는 when 은 if-else if 사슬과 같은 순서로 판정합니다.

대신 분기 조건이 참이나 거짓, 곧 Boolean 이어야 합니다. Boolean 은 자바의 boolean 에 해당하는 코틀린 타입입니다. 인자 없는 when 에 score -> 를 쓰면 kotlinc 가 error: condition type mismatch: inferred type is 'Int' but 'Boolean' was expected. 로 멈춥니다.

3. is 매칭과 스마트 캐스트

타입에 따라 갈라지는 코드는 자바에서 instanceof 사슬로 써 왔습니다. JDK 21 부터는 switch 의 case 에 타입 패턴을 쓸 수 있습니다(JEP 441). 이 절에서는 같은 함수를 코틀린의 is(타입 검사) 분기와 JDK 21 패턴 switch 로 써서 맞댑니다.

Kotlin
fun describe(x: Any) {
    when (x) {
        is String -> println("String, length " + x.length)
        is Int -> println("Int, plus one " + (x + 1))
        else -> println("something else")
    }
}

fun main() {
    describe("kotlin")  // String, length 6
    describe(41)        // Int, plus one 42
    describe(2.5)       // something else
}
  • x: Any — Any 는 모든 코틀린 타입의 부모 타입입니다. 자바에서 Object 로 받던 매개변수에 씁니다
  • is String — x 가 String 이면 맞습니다. 자바의 instanceof 입니다
  • x.length — 문자열 길이입니다. x 는 Any 인데 캐스트 없이 불렀습니다
  • x + 1 — Int 분기에서도 캐스트 없이 x 에 1 을 더했습니다

같은 일을 JDK 21 패턴 switch 로 쓴 자바입니다. main 은 코틀린과 같은 세 번의 호출이라 ... 로 줄였습니다.

Java
public class Is {
    static void describe(Object x) {
        switch (x) {
            case String s ->
                System.out.println("String, length " + s.length());
            case Integer i ->
                System.out.println("Int, plus one " + (i + 1));
            default ->
                System.out.println("something else");
        }
    }
    ...
}

코틀린 쪽은 이렇게 돌렸습니다.

터미널
$ kotlinc Is.kt -d out
$ kotlin -cp out IsKt

자바 Is.java 도 같은 세 줄을 찍었습니다. 여기서 볼 것은 분기 안에서 무엇으로 값을 쓰느냐 하나입니다.

자바는 case String s 로 새 변수 s 를 만들고, 분기 안에서 x 대신 s 를 씁니다. 코틀린은 새 변수 없이 x 를 그대로 씁니다. is String 이 맞은 분기 안에서는 컴파일러가 x 를 String 으로 다루기 때문입니다. 이렇게 타입 검사를 통과한 변수를 컴파일러가 그 타입으로 좁혀 주는 기능을 스마트 캐스트(smart cast)라고 부릅니다.

스마트 캐스트는 검사와 사용 사이에 값이 바뀔 수 없는 변수에서만 됩니다. 위의 x 는 매개변수라 바뀌지 않습니다.

바뀔 수 있는 쪽은 막힙니다. 클래스에 딸린 var(다시 대입할 수 있는 변수) v 를 when 으로 검사하고 그 분기 안에서 length 를 부른 VarProp.kt 는 컴파일되지 않았습니다. 에러는 error: smart cast to 'String' is impossible, because 'v' is a mutable property that could be mutated concurrently. 입니다. 검사한 뒤 다른 코드가 v 를 바꿀 수 있다는 뜻입니다.

그럼 캐스트는 누가 쓸까요. 컴파일러가 대신 써 넣습니다. 컴파일 결과인 .class 파일을 자바 코드로 되읽어 주는 디컴파일러 CFR 로 그 자리를 확인했습니다.

터미널
$ java -jar cfr.jar out/IsKt.class
...
public final class IsKt {
    public static final void describe(@NotNull Object x) {
        Intrinsics.checkNotNullParameter((Object)x, (String)"x");
        Object object = x;
        if (object instanceof String) {
            System.out.println((Object)("String, length " + ((String)x).length()));
        } else if (object instanceof Integer) {
            System.out.println((Object)("Int, plus one " + (((Number)x).intValue() + 1)));
        } else {
            System.out.println((Object)"something else");
        }
    }
...
  • instanceof 사슬 — is 분기가 if-else if 와 instanceof 로 바뀌었습니다
  • ((String)x).length() — 소스에 없던 캐스트입니다. 스마트 캐스트는 컴파일러가 캐스트를 대신 써 넣는 것입니다
  • ((Number)x).intValue() — Any 에 담긴 정수는 Integer 객체라, 캐스트한 뒤 int 값을 꺼냅니다. Number 는 Integer 의 부모 클래스입니다
  • Intrinsics.checkNotNullParameter — 인자가 null 이면 멈추는 검사입니다. Any 는 null 을 받지 않는 타입이라 붙었습니다
  • @NotNull — 매개변수나 반환값이 null 이 아니라는 표시로 컴파일러가 붙인 애노테이션입니다

CFR 출력은 이렇게 줄여 실었습니다. 맨 위의 머리 주석, import, 한 줄이 아주 긴 @Metadata(...)(코틀린 컴파일러가 남기는 부가 정보)를 ... 로 걷었습니다. 디컴파일 결과는 원래 소스를 되살린 것이 아니라 클래스 파일에 적힌 내용을 자바 문법으로 다시 적은 것입니다.

같은 일을 자바는 다른 길로 합니다. 자바 쪽 Is.class 를 CFR 로 열면 instanceof 사슬이 없습니다. java.lang.runtime.SwitchBootstraps.typeSwitch 라는 JDK 메서드에 String.class, Integer.class 목록을 넘겨 몇 번째 case 가 맞는지 받아 온 뒤, 그 번호로 switch 를 가릅니다.

분기 순서 규칙은 두 언어가 갈립니다. String 의 부모 타입인 is CharSequence 를 is String 보다 위에 둔 코틀린 Order.kt 는 경고 없이 컴파일됐고, "kotlin" 을 넣으면 CharSequence 를 찍었습니다. 같은 순서의 자바는 error: this case label is dominated by a preceding case label 로 거절했습니다. 앞 case 가 뒤 case 를 가려 뒤는 영영 실행될 수 없다는 뜻입니다.

정리하면, 코틀린의 is 분기는 instanceof 사슬로 컴파일됩니다. 분기 안의 캐스트는 컴파일러가 써 넣습니다. 앞 분기에 가려진 분기를 자바는 에러로 막지만, 코틀린은 통과시킵니다.

4. in 범위 매칭

나이·점수·코드 번호처럼 값이 어느 구간에 드느냐로 가르는 일도 흔합니다. 자바 switch 는 구간을 case 로 못 쓰니 보통 if 사슬로 갑니다. 코틀린은 분기 조건에 in 과 범위를 씁니다.

Kotlin
fun describe(age: Int) {
    when (age) {
        in 0..12 -> println("child")
        in 13..19 -> println("teen")
        !in 0..150 -> println("invalid")
        else -> println("adult")
    }
}

fun main() {
    describe(15)   // teen
    describe(200)  // invalid
    describe(40)   // adult
}
  • in 0..12 — age 가 0 이상 12 이하면 맞습니다. .. 는 양 끝을 포함하는 범위를 만듭니다
  • !in 0..150 — 범위 밖이면 맞습니다
  • 범위를 만드는 다른 방법과 범위 객체의 속은 KB 10 범위와 반복 편에서 봅니다
터미널
$ kotlinc Age.kt -d out
$ kotlin -cp out AgeKt

15 는 13..19 에, 200 은 !in 0..150 에 맞았습니다. 40 은 네 분기 중 어디에도 안 맞아 else 로 갔습니다.

자바 쪽에는 구간을 적는 case 가 없습니다. 대신 JDK 21 패턴 switch 에서 case 뒤에 조건을 더 붙이는 when 절을 씁니다. 코틀린의 when 과 이름만 같은 자바 키워드입니다.

이걸로 옮긴 Age.java 는 case Integer a when a >= 13 && a <= 19 -> 처럼 구간마다 조건을 손으로 붙였습니다. int age 를 받은 채로는 error: incompatible types: pattern of type Integer is not applicable at int 로 멈췄고, 매개변수를 Integer age 로 바꾼 Age2.java 는 컴파일돼 코틀린과 같은 teen·invalid·adult 를 찍었습니다.

그래도 자바 쪽은 구간마다 a >= 13 && a <= 19 처럼 양 끝 비교를 손으로 씁니다. 코틀린은 in 13..19 한 마디입니다.

5. 식으로 쓰는 when 과 else

지금까지는 분기마다 println 을 불렀습니다. when 은 값을 돌려주는 식으로도 쓸 수 있습니다. 이 절에서는 식으로 쓸 때 else 가 왜 필요한지, enum 이면 왜 빼도 되는지, 컴파일이 끝난 뒤 상수가 늘면 어떻게 되는지, 문으로 쓰면 무엇이 달라지는지를 에러 원문과 실행으로 봅니다.

Int 값에 else 가 없으면

Kotlin
fun name(x: Int): String {
    val s = when (x) {
        1 -> "one"
        2 -> "two"
    }
    return s
}
  • fun name(x: Int): String — 괄호 뒤 : String 이 반환 타입입니다
  • val s = when (x) { ... } — 맞은 분기의 화살표 오른쪽 값이 when 전체의 값이 되어 s 에 들어갑니다. 자바의 switch 식에 해당합니다
터미널
$ kotlinc Expr.kt -d out
Expr.kt:2:13: error: 'when' expression must be exhaustive. Add an 'else' branch.
    val s = when (x) {
            ^^^^

when 식은 모든 경우를 빠짐없이 다뤄야 하니 else 분기를 더하라는 에러입니다. x 가 3 이면 s 에 넣을 값이 없기 때문입니다. Int 가 담을 수 있는 값은 약 43억 가지입니다. 분기 조건 몇 개로 그 전부를 짚을 수는 없으니, 나머지를 받을 else 가 있어야 합니다.

같은 모양의 자바 switch 식(String s = switch (x) { case 1 -> "one"; case 2 -> "two"; };)도 error: the switch expression does not cover all possible input values 로 거절됩니다.

enum 이면 else 를 뺄 수 있다

값의 가짓수를 컴파일러가 전부 아는 타입이면 이야기가 달라집니다. 코틀린의 enum class 는 자바의 enum 과 같은 열거형입니다.

Kotlin
// Color.kt
enum class Color { RED, GREEN, BLUE }

// Paint.kt
fun label(c: Color): String {
    return when (c) {
        Color.RED -> "red"
        Color.GREEN -> "green"
        Color.BLUE -> "blue"
    }
}
  • enum class Color { RED, GREEN, BLUE } — 상수 셋을 가진 열거형입니다

kotlinc Color.kt Paint.kt -d out 은 else 가 없는데도 에러 없이 끝났습니다. 상수 셋을 모두 분기로 짚었기 때문입니다. BLUE 분기를 지운 Missing.kt 로 바꿔 컴파일했습니다.

터미널
$ kotlinc Color.kt Missing.kt -d out
Missing.kt:2:12: error: 'when' expression must be exhaustive. Add the 'BLUE' branch or an 'else' branch.
    return when (c) {
           ^^^^

에러가 빠진 상수 이름 BLUE 를 짚었습니다. Int 와 달리 Color 의 값은 RED·GREEN·BLUE 셋뿐이라, 컴파일러가 무엇이 빠졌는지 셀 수 있습니다.

자바도 같습니다. 상수 셋을 모두 case 로 짚은 switch 식은 default 없이 컴파일됐습니다. 하나를 빼면 Int 때와 같은 the switch expression does not cover all possible input values 에러로 멈췄습니다.

없던 상수가 실행 시점에 오면

빠짐없이 짚었는지 세는 이 검사는 컴파일할 때 하는 것입니다. 그런데 Color.kt 와 Paint.kt 는 따로 컴파일할 수 있습니다. label 을 컴파일한 뒤에 상수가 늘어나면 어떻게 되는지 해 봤습니다.

Color 에 YELLOW 를 더해 Color.kt 만 다시 컴파일했습니다. 그다음 Color.valueOf("YELLOW") 로 만든 값을 옛 PaintKt.class 의 label 에 넘겼습니다. 결과는 kotlin.NoWhenBranchMatchedException 이었습니다. label 은 RED·GREEN·BLUE 셋만 짚은 채로 컴파일돼 있어서, 그 셋 중 어디에도 맞지 않는 값이 오면 이 예외가 납니다.

같은 실험에서 자바는 java.lang.MatchException 을 던졌습니다.

else 를 뺀 enum when 은 컴파일할 때 빠짐없이 짚었는지 검사한 것입니다. 실행 시점에 모르는 상수가 오면 예외로 끝납니다.

문으로 쓰면 타입에 따라 갈린다

println 만 부르는 문으로 쓸 때는 돌려줄 값이 없습니다. 그래도 else 를 빼도 되는지는 괄호 안 값의 타입에 따라 갈립니다.

Any·Int 처럼 가짓수가 열린 타입이면, 문에서 어느 분기에도 안 맞는 값은 그냥 지나갑니다. 3절처럼 is String·is Int 분기만 두고 else 를 뺀 NoElse.kt 에 2.5 를 넘기고, 그다음 줄에 println("end") 를 두었습니다.

터미널
$ kotlinc NoElse.kt -d out
$ kotlin -cp out NoElseKt
end

컴파일도 실행도 막히지 않았고, 분기에서는 아무것도 찍히지 않았습니다. 1 -> 분기 하나만 둔 Int 문 IntStmt.kt 에 7 을 넣었을 때도 end 만 나왔습니다.

자바는 여기서 다릅니다. default 를 뺀 패턴 switch 문 NoDefault.java 는 error: the switch statement does not cover all possible input values 로 거절됐습니다. JDK 21 은 패턴을 쓴 switch 라면 문이어도 모든 값을 짚게 합니다.

괄호 안 값이 enum·Boolean·sealed 클래스처럼 가짓수가 정해진 타입이면, 코틀린도 문에서 빠짐없이 짚어야 합니다. sealed 클래스는 자식 클래스 목록을 컴파일러가 전부 아는 클래스입니다. 이런 타입은 빠진 경우를 컴파일러가 셀 수 있어서, 코틀린 1.7 부터 문에서 빠뜨려도 에러를 냅니다.

Color 를 받아 RED·GREEN 두 분기만 println 으로 짚은 문 Stmt.kt 는 Add the 'BLUE' branch or an 'else' branch. 에러로 멈췄습니다. true 분기 하나만 둔 Boolean 문 BoolStmt.kt 도 Add the 'false' branch or an 'else' branch. 에러로 멈췄습니다.

정리하면, when 을 식으로 쓰면 모든 값에 결과가 있어야 해서 else 가 필요합니다. enum 과 Boolean 처럼 가짓수가 정해진 타입은 전부 짚으면 else 를 뺄 수 있습니다. Boolean 값을 true·false 두 분기로 가른 when 식 Bool.kt 는 else 없이 통과했습니다. 이런 타입은 문으로 써도 빠짐없이 짚어야 합니다.

6. 한 장 요약

when 은 맞은 분기 하나만 실행하는 switch 라 break 도 fall-through 도 없고, 분기 조건에 여러 값·변수·식·is 타입·in 범위를 쓴다. 식으로 쓰면 모든 값을 다뤄야 해서 else 가 필요하지만 enum 을 전부 짚으면 뺄 수 있고, enum·Boolean 은 문으로 써도 빠짐없이 짚어야 한다.

하는 일 자바 (JDK 21) 코틀린 확인한 방법
분기 하나만 실행 옛 case 1: 은 break 필요 · 화살표 case 1 -> 는 불필요 1 ->. fall-through 없음 실행
반복 안의 break switch 만 빠져나감 바깥 반복문을 끝냄 실행
여러 값 case 1, 2 -> 1, 2 -> 실행
변수·식 조건 constant expression required limit -> · limit * 2 -> 가능 javac 에러 · 실행
같은 값 두 번 duplicate case label 에러 경고, 첫 분기가 이김 컴파일 · 실행
조건 사슬 if-else if 인자 없는 when { }. 조건은 Boolean 실행 · kotlinc 에러
타입 분기 case String s -> 새 변수 is String -> 스마트 캐스트. 캐스트는 컴파일러가 씀 실행 · CFR
가려진 분기 dominated by a preceding case label 에러 경고 없이 앞 분기가 이김 컴파일 · 실행
구간 분기 Integer 값에 case Integer a when ... · int 값은 에러 in 0..12 -> · !in javac 에러 · 실행
식에서 빠진 경우 does not cover all possible input values must be exhaustive. Add an 'else' branch. 컴파일 에러
문에서 빠진 경우 패턴 switch 문은 does not cover 에러 Any·Int 는 그냥 지나감 · enum·Boolean 은 에러 컴파일 에러 · 실행
enum 을 전부 짚음 default 불필요. 실행 시 새 상수면 MatchException else 불필요. 문으로 써도 빠지면 에러. 새 상수면 NoWhenBranchMatchedException 컴파일 에러 · 실행

관련 항목

when 이 대신하는 자바의 분기 문법

switch · switch 식 · fall-through · if-else 사슬 · 패턴 매칭 switch · JEP 361 · JEP 441

when 의 분기 조건에 쓰는 코틀린 문법

when · is 연산자 · in 연산자 · 범위 연산자 · 스마트 캐스트 · else 분기

when 식이 빠짐없이 다뤄야 하는 타입

완전성 검사 · enum class · 열거형 · Boolean · sealed class · sealed interface

빠진 분기를 실행 시점에 알리는 예외

NoWhenBranchMatchedException · MatchException · 분리 컴파일

when 이 컴파일되어 바뀌는 JVM 코드

instanceof · 캐스트 · SwitchBootstraps · Intrinsics · 널 검사

클래스 파일을 열어 보는 도구

javap · 디컴파일 · 디컴파일러 · CFR · 클래스 파일

이 편에 쓰인 언어와 개발 도구

코틀린 · 자바 · JDK · 컴파일러 · kotlinc · 가상 머신 · 코틀린 표준 라이브러리