Kotlin の when 式とは、1つの値や複数の条件を上から順に調べ、最初に当てはまった分岐の結果を返す構文です。 Java の switch に相当しますが、break が不要で、範囲(in)や型(is)でも分岐でき、式として値をそのまま変数に入れられます。

この記事では、when の書き方を7種類の一覧表にまとめ、すべてのコードに実行結果を付けました。さらに、Java の switch との対応表、sealed interface と enum で分岐を1つ消したときに出る 'when' expression must be exhaustive のエラー文、そして「else を書かないほうがよい」理由を、型を1つ追加する流れで確かめます。

確認環境:Ubuntu 24.04 / Kotlin 2.4.20(kotlinc)/ OpenJDK 21.0.10。掲載した出力とエラー文は、すべてこの環境でコンパイル・実行して得たものです。

when式の書き方の早見表

やりたいこと 書き方 例 当てはまる条件
値で分岐 値 -> 1 -> "one" subject がその値と等しい
複数の値をまとめる 値, 値 -> "Sat", "Sun" -> "休日" どれか1つと等しい
範囲で分岐 in 範囲 -> in 90..100 -> "A" 範囲やコレクションに含まれる
範囲の外 !in 範囲 -> !in 0..100 -> "範囲外" 含まれない
型で分岐 is 型 -> is String -> v.length その型である(右側で型が確定する)
条件式を並べる when { 条件 -> } stock == 0 -> "在庫なし" 条件が true
値を変数に受ける when (val x = 式) when (val code = fetchStatus(id)) 分岐の中で x を使える
条件を追加する 条件 if 追加条件 -> is Int if v < 0 -> "負の数" 両方が true(Kotlin 2.2 以降)
どれにも当てはまらない else -> else -> "other" 上のどれも当てはまらない

subject(サブジェクト) とは、when (x) のかっこの中に書く、調べる対象の値のことです。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin when式の使い方|switchとの違い・型判定・sealed」

まず動かす:7つの書き方を1つのファイルで確認する

次のコードを forms.kt という名前で保存すると、そのままコンパイルして実行できます。7つの関数が、早見表の書き方にそれぞれ対応しています。

// forms.kt : when の書き方 7 種類
fun byValue(x: Int): String = when (x) {
    1 -> "one"
    2 -> "two"
    else -> "other"
}

fun byMultiple(day: String): String = when (day) {
    "Sat", "Sun" -> "休日"
    else -> "平日"
}

fun byRange(score: Int): String = when (score) {
    in 90..100 -> "A"
    in 70..<90 -> "B"
    !in 0..100 -> "範囲外"
    else -> "C"
}

fun byType(v: Any): String = when (v) {
    is String -> "String 長さ=${v.length}"
    is Int -> "Int 2倍=${v * 2}"
    is List<*> -> "List 件数=${v.size}"
    else -> "その他"
}

fun noSubject(stock: Int, reserved: Int): String = when {
    stock == 0 -> "在庫なし"
    stock - reserved < 5 -> "残りわずか"
    else -> "在庫あり"
}

fun fetchStatus(id: Int): Int = if (id == 1) 200 else 404

fun subjectVar(id: Int): String = when (val code = fetchStatus(id)) {
    200 -> "OK"
    else -> "エラー code=$code"
}

fun guard(v: Any): String = when (v) {
    is String if v.isEmpty() -> "空の文字列"
    is String -> "文字列: $v"
    is Int if v < 0 -> "負の数"
    is Int -> "0以上の数"
    else -> "その他"
}

fun main() {
    println("値      : ${byValue(1)} / ${byValue(9)}")
    println("複数値  : ${byMultiple("Sun")} / ${byMultiple("Mon")}")
    println("範囲    : ${byRange(95)} / ${byRange(70)} / ${byRange(50)} / ${byRange(120)}")
    println("型      : ${byType("abc")} / ${byType(21)} / ${byType(listOf(1, 2))} / ${byType(1.5)}")
    println("引数なし: ${noSubject(0, 0)} / ${noSubject(10, 7)} / ${noSubject(50, 3)}")
    println("変数    : ${subjectVar(1)} / ${subjectVar(2)}")
    println("ガード  : ${guard("")} / ${guard("abc")} / ${guard(-3)} / ${guard(8)}")
}

コンパイルと実行のコマンドは次のとおりです。-Dstdout.encoding=UTF-8 は、日本語の出力を UTF-8 で表示するための指定です。

kotlinc forms.kt -include-runtime -d forms.jar
java -Dstdout.encoding=UTF-8 -jar forms.jar

実行結果:

値      : one / other
複数値  : 休日 / 平日
範囲    : A / B / C / 範囲外
型      : String 長さ=3 / Int 2倍=42 / List 件数=2 / その他
引数なし: 在庫なし / 残りわずか / 在庫あり
変数    : OK / エラー code=404
ガード  : 空の文字列 / 文字列: abc / 負の数 / 0以上の数

7つの書き方は、同じ when の中で自由に混ぜられます。 以降のセクションで、1つずつ要点を見ていきます。

値・複数の値で分岐する

いちばん基本の形です。-> の左に値、右に結果を書きます。

fun byValue(x: Int): String = when (x) {
    1 -> "one"
    2 -> "two"
    else -> "other"
}

複数の値で同じ結果にしたいときは、カンマで並べます。Java の switch で case を縦に並べる書き方に当たります。

fun byMultiple(day: String): String = when (day) {
    "Sat", "Sun" -> "休日"
    else -> "平日"
}

-> の左側には、リテラル(数値や文字列をそのまま書いたもの)だけでなく、定数・関数の戻り値・in によるコレクションの判定も書けます。次のコードで確かめます。

// extra.kt(抜粋して単体で動く形)
const val MAX = 100

fun limit(): Int = 50

fun check(x: Int): String = when (x) {
    MAX -> "定数 MAX と同じ"
    limit() -> "関数 limit() の戻り値と同じ"
    in listOf(1, 3, 5) -> "リストに含まれる"
    in 10..20 step 5 -> "10・15・20 のどれか"
    else -> "どれでもない"
}

fun main() {
    println(listOf(100, 50, 3, 15, 12).map { check(it) })
}

実行結果:

[定数 MAX と同じ, 関数 limit() の戻り値と同じ, リストに含まれる, 10・15・20 のどれか, どれでもない]

Java の従来の switch では case に定数しか書けませんが、Kotlin の when は任意の式を書けます。 この自由さが、when が幅広く使われる理由の1つです。

分岐の中に複数の文を書く

-> の右側を波かっこで囲むと、複数の文を書けます。ブロックの最後の式が、その分岐の値になります。

fun block(x: Int): String = when {
    x % 2 == 0 -> {
        val half = x / 2
        "偶数(半分は $half)"
    }
    else -> "奇数"
}

fun main() {
    println("${block(8)} / ${block(7)}")
}

実行結果:

偶数(半分は 4) / 奇数

範囲(in)で分岐する

in を使うと、値が範囲に入っているかで分岐できます。点数や年齢、件数のように「〜以上〜未満」で分けたいときに向いています。

fun byRange(score: Int): String = when (score) {
    in 90..100 -> "A"
    in 70..<90 -> "B"
    !in 0..100 -> "範囲外"
    else -> "C"
}
書き方 意味 例の範囲
in 90..100 90 以上 100 以下 両端を含む
in 70..<90 70 以上 90 未満 終わりを含まない
!in 0..100 0〜100 に入らない 範囲の外
in listOf(1, 3, 5) リストに含まれる コレクションにも使える
in 10..20 step 5 10・15・20 のどれか 刻み幅つき

上から順に調べ、最初に当てはまった分岐だけが動く

範囲が重なっている場合は、上に書いた分岐が優先されます。次の例では 7 が両方の範囲に入りますが、先に書いた「1〜10」だけが選ばれます。

fun order(x: Int): String = when (x) {
    in 1..10 -> "1〜10"
    in 5..20 -> "5〜20"
    else -> "その他"
}

fun main() {
    println("${order(7)} / ${order(15)}")
}

実行結果:

1〜10 / 5〜20

条件が狭いもの・特別なものを上に、広いものを下に書くと、意図どおりに動きます。

型(is)で分岐する:スマートキャストが効く

is を使うと、値の型で分岐できます。-> の右側では、その型として扱えるので、キャスト(型の変換)を自分で書く必要がありません。この仕組みをスマートキャストと呼びます。

fun byType(v: Any): String = when (v) {
    is String -> "String 長さ=${v.length}"   // v は String として使える
    is Int -> "Int 2倍=${v * 2}"             // v は Int として使える
    is List<*> -> "List 件数=${v.size}"
    else -> "その他"
}

実行結果(byType("abc")、byType(21)、byType(listOf(1, 2))、byType(1.5) の順):

型      : String 長さ=3 / Int 2倍=42 / List 件数=2 / その他

v の型は Any ですが、is String の分岐の中では v.length を呼べています。!is String と書けば「その型ではない」という条件になります。

null を分岐に書く

null 許容型(String? のように null が入るかもしれない型)では、null をそのまま分岐の値に書けます。null の分岐を通ったあとの分岐では、値は null でないものとして扱われます。

fun nullable(v: String?): String = when (v) {
    null -> "null"
    "" -> "空"
    else -> "長さ ${v.length}"   // ここでは v は String
}

fun main() {
    println("${nullable(null)} / ${nullable("")} / ${nullable("abc")}")
}

実行結果:

null / 空 / 長さ 3

引数なしの when:if - else if の置き換え

かっこと subject を省略すると、各分岐に Boolean の条件式を書けます。複数の変数が関わる条件や、大小比較を並べたいときに使います。

fun noSubject(stock: Int, reserved: Int): String = when {
    stock == 0 -> "在庫なし"
    stock - reserved < 5 -> "残りわずか"
    else -> "在庫あり"
}

if - else if が続くコードは、引数なしの when に置き換えると見通しがよくなります。直す前と後を並べます。

// 直す前: if - else if の連なり
fun gradeIf(score: Int): String {
    if (score >= 90) {
        return "A"
    } else if (score >= 70) {
        return "B"
    } else if (score >= 50) {
        return "C"
    } else {
        return "D"
    }
}

// 直した後: when 式
fun gradeWhen(score: Int): String = when {
    score >= 90 -> "A"
    score >= 70 -> "B"
    score >= 50 -> "C"
    else -> "D"
}

fun main() {
    val scores = listOf(95, 75, 55, 10)
    println(scores.map { gradeIf(it) } == scores.map { gradeWhen(it) })
    println(scores.map { gradeWhen(it) })
}

実行結果:

true
[A, B, C, D]

結果は同じで、行数は11行から6行になりました。条件と結果が1行ずつ対応するので、表を読むように確認できます。

when は式なので、文字列テンプレートの中や関数の引数にも直接書けます。

fun main() {
    val n = 7
    println("$n は ${when { n % 2 == 0 -> "偶数"; else -> "奇数" }}")
}

実行結果:

7 は 奇数

subject を変数に受ける:when (val x = ...)

関数の戻り値で分岐し、その値を分岐の中でも使いたいときは、かっこの中で変数を宣言できます。

fun fetchStatus(id: Int): Int = if (id == 1) 200 else 404

fun subjectVar(id: Int): String = when (val code = fetchStatus(id)) {
    200 -> "OK"
    else -> "エラー code=$code"
}

実行結果(subjectVar(1) と subjectVar(2)):

変数    : OK / エラー code=404

公式ドキュメントには、この変数のスコープ(使える範囲)は when の本体に限られると書かれています。when の外に変数を残さないので、同じ名前を別の場所で使っても混ざりません。

null かどうかの確認と組み合わせる使い方も便利です。throw を分岐に書くこともできます。

fun parse(s: String): Int = when (val n = s.toIntOrNull()) {
    null -> throw IllegalArgumentException("数値ではありません: $s")
    else -> n          // ここでは n は Int
}

fun main() {
    println(parse("42"))
    try {
        parse("abc")
    } catch (e: IllegalArgumentException) {
        println("例外: ${e.message}")
    }
}

実行結果:

42
例外: 数値ではありません: abc

ガード条件:分岐に if で条件を足す(Kotlin 2.2 以降)

ガード条件とは、分岐の条件のうしろに if 追加条件 を書いて、条件を絞り込む書き方です。Kotlin 公式の「What's new in Kotlin 2.2.0」には、2.1.0 でプレビューとして入り、2.2.0 で Stable(安定版)になったと書かれています。

fun guard(v: Any): String = when (v) {
    is String if v.isEmpty() -> "空の文字列"
    is String -> "文字列: $v"
    is Int if v < 0 -> "負の数"
    is Int -> "0以上の数"
    else -> "その他"
}

実行結果(guard("")、guard("abc")、guard(-3)、guard(8) の順):

ガード  : 空の文字列 / 文字列: abc / 負の数 / 0以上の数

is String if v.isEmpty() は「String であり、かつ空である」という意味です。is String の判定が先に行われるので、ガード条件の中でも v を String として使えます。

else if も書ける

else にもガード条件を付けられます。

fun guardElseIf(v: Any, strict: Boolean): String = when (v) {
    is Int if v > 100 -> "大きい数"
    is Int -> "数"
    else if strict -> "数以外(厳格)"
    else -> "数以外"
}

fun main() {
    println("${guardElseIf(500, false)} / ${guardElseIf(5, false)} / ${guardElseIf("a", true)} / ${guardElseIf("a", false)}")
}

実行結果:

大きい数 / 数 / 数以外(厳格) / 数以外

ガード条件を書ける場所の決まり

ガード条件には2つの決まりがあります。実際にコンパイルして、エラー文を確かめました。

fun c(v: Any): String = when (v) {
    is String, is Int if v.toString().isEmpty() -> "empty"   // カンマ区切りと併用
    else -> "other"
}

fun d(x: Int): String = when {
    x > 0 if x < 10 -> "1桁"                                  // 引数なしの when で使用
    else -> "other"
}

コンパイル結果:

misc_ng.kt:15:23: error: use of comma in 'when' condition with guard statement is not allowed.
misc_ng.kt:20:11: error: guard statements are only allowed in 'when' with subject.
決まり エラー文 書き換え方
カンマ区切りの分岐には付けられない use of comma in 'when' condition with guard statement is not allowed. 分岐を2行に分ける
subject のある when だけで使える guard statements are only allowed in 'when' with subject. 引数なしの when では x > 0 && x < 10 と書く

Java の switch との違い:対応表

Java から Kotlin に移るときに、最初に知っておきたい違いを表にしました。

比べる点 Java の従来の switch 文 Java 14 以降の switch 式 Kotlin の when
キーワード switch / case / default switch / case -> / default when / -> / else
break 必要(書かないと次の case へ進む) 不要 不要(次の分岐へ進む仕組みが無い)
値を返す 返さない(文) 返す 返す(文としても使える)
複数の値 case を縦に並べる case 1, 2 -> 1, 2 ->
範囲 書けない 書けない in 1..10 ->
型で分岐 書けない Java 21 から case String s -> is String ->
条件の追加 書けない Java 21 から case X x when 条件 -> is X if 条件 ->(2.2 以降)
条件式だけを並べる 書けない 書けない when { 条件 -> }
その他の分岐 default(省略可) default(網羅できていれば省略可) else(網羅できていれば省略可)

Java の when は switch のガードに使うキーワードで、Kotlin の when は分岐の構文そのものです。同じ単語でも役割が違う点に気をつけると、読み替えがスムーズです。

同じ処理を Java と Kotlin で動かす

Java 21 で、従来の switch 文と switch 式を並べて実行しました。

// Sw.java
public class Sw {
    public static void main(String[] args) {
        int x = 1;
        // 従来の switch 文: break を書かないと次の case へ進む
        switch (x) {
            case 1:
                System.out.println("one");
            case 2:
                System.out.println("two");
                break;
            default:
                System.out.println("other");
        }
        // Java 14 以降の switch 式: -> で書くと break 不要で値を返す
        String s = switch (x) {
            case 1 -> "one";
            case 2 -> "two";
            default -> "other";
        };
        System.out.println(s);
    }
}

実行結果(java Sw.java):

one
two
one

case 1: に break が無いため、1行目と2行目で "one" と "two" の両方が出力されています。この「次の case へ進む」動きをフォールスルーと呼びます。3行目は switch 式の結果です。

同じ処理を Kotlin で書きます。

// sw.kt
fun main() {
    val x = 1
    val s = when (x) {          // 式として使う: else が必要
        1 -> "one"
        2 -> "two"
        else -> "other"
    }
    println(s)
    when (x) {                  // 文として使う: else を省略できる
        1 -> println("one")
        2 -> println("two")
    }
}

実行結果:

one
one

Kotlin の when にはフォールスルーが無く、当てはまった分岐だけが動きます。 複数の値で同じ処理をしたいときは、カンマで並べます。

Java 21 のパターンマッチングとの対応

Java 21 の switch は、sealed interface の型で分岐し、when でガードを付けられます。Kotlin の is とガード条件に対応する書き方です。

// Pm.java
public class Pm {
    sealed interface Result permits Success, Failure, Loading {}
    record Success(String body) implements Result {}
    record Failure(int code) implements Result {}
    record Loading() implements Result {}

    static String describe(Result r) {
        return switch (r) {
            case Success s -> "成功: " + s.body();
            case Failure f when f.code() >= 500 -> "サーバー側のエラー: " + f.code();
            case Failure f -> "エラー: " + f.code();
            case Loading l -> "読み込み中";
        };
    }

    public static void main(String[] args) {
        System.out.println(describe(new Success("hello")));
        System.out.println(describe(new Failure(503)));
        System.out.println(describe(new Failure(404)));
        System.out.println(describe(new Loading()));
    }
}

実行結果:

成功: hello
サーバー側のエラー: 503
エラー: 404
読み込み中

case Loading l の行を消してコンパイルすると、Java でも網羅チェックのエラーが出ます。

Pm.java:8: error: the switch expression does not cover all possible input values

Java と Kotlin のどちらも「すべての型を扱っているか」をコンパイラが調べてくれます。次のセクションで、Kotlin 側の動きを詳しく見ます。

else はいつ必要か:式と文の違い

when を式として使う(値を受け取る)ときは、すべての場合を網羅する必要があります。網羅できていないと、コンパイルエラーになります。

fun a(x: Int): String = when (x) {
    1 -> "one"
    2 -> "two"
}

コンパイル結果:

misc_ng.kt:3:25: error: 'when' expression must be exhaustive. Add an 'else' branch.
fun a(x: Int): String = when (x) {
                        ^^^^

Int はすべての値を列挙できないので、else を足すよう案内されます。

一方、文として使う(値を受け取らない)ときは、Int や String が subject なら else を省略できます。当てはまる分岐が無ければ、何も実行せずに次へ進みます。

fun stmt(x: Int) {
    when (x) {
        1 -> println("one")
        2 -> println("two")
    }
    println("stmt($x) 終了")
}

fun main() {
    stmt(1)
    stmt(3)
}

実行結果:

one
stmt(1) 終了
stmt(3) 終了

else の要否の一覧

subject の型 式として使う 文として使う
Int・String など else が必要 else を省略できる
Boolean true と false を書けば else 不要 true と false の両方か else が必要
enum class 全部の定数を書けば else 不要 全部の定数か else が必要
sealed class / sealed interface 全部の型を書けば else 不要 全部の型か else が必要
上の3つの null 許容型 null -> も書けば else 不要 null -> も含めた全部か else が必要

enum を subject にした when は、文として使う場合も網羅が求められます。次のコードで確かめました。

enum class Size { S, M, L }

fun b(size: Size) {
    when (size) {
        Size.S -> println("small")
    }
}

コンパイル結果:

misc_ng.kt:9:5: error: 'when' expression must be exhaustive. Add the 'M', 'L' branches or an 'else' branch.
    when (size) {
    ^^^^

Boolean と null 許容の enum は、else なしで次のように書けます。

enum class Size { S, M, L }

fun label(size: Size?): String = when (size) {
    Size.S -> "小"
    Size.M -> "中"
    Size.L -> "大"
    null -> "未選択"
}

fun flag(on: Boolean): String = when (on) {
    true -> "ON"
    false -> "OFF"
}

fun main() {
    println("${label(Size.M)} / ${label(null)}")
    println("${flag(true)} / ${flag(false)}")
}

実行結果:

中 / 未選択
ON / OFF

sealed interface と enum:分岐を1つ消すとどうなるか

sealed interface(シールドインターフェース)とは、実装する型の種類をあらかじめ決めておけるインターフェースです。コンパイラが「この型は全部で何種類か」を知っているため、when で全種類を扱っているかを調べられます。

まず、すべての分岐がそろった動くコードです。

// sealed_ok.kt
sealed interface Result
data class Success(val body: String) : Result
data class Failure(val code: Int) : Result
data object Loading : Result

enum class Size { S, M, L }

fun describe(r: Result): String = when (r) {
    is Success -> "成功: ${r.body}"
    is Failure -> "エラー: ${r.code}"
    Loading -> "読み込み中"
}

fun price(size: Size): Int = when (size) {
    Size.S -> 300
    Size.M -> 400
    Size.L -> 500
}

fun main() {
    println(describe(Success("hello")))
    println(describe(Failure(404)))
    println(describe(Loading))
    println(Size.entries.map { "$it=${price(it)}" })
}

実行結果:

成功: hello
エラー: 404
読み込み中
[S=300, M=400, L=500]

data object(データオブジェクト)は、インスタンスが1つだけの型です。型ではなく値として比べられるので、is を付けずに Loading -> と書けます。

分岐を1つずつ消してコンパイルする

Loading -> "読み込み中" の行と Size.L -> 500 の行を消して、コンパイルしました。

sealed_ng.kt:8:35: error: 'when' expression must be exhaustive. Add the 'Loading' branch or an 'else' branch.
fun describe(r: Result): String = when (r) {
                                  ^^^^
sealed_ng.kt:13:30: error: 'when' expression must be exhaustive. Add the 'L' branch or an 'else' branch.
fun price(size: Size): Int = when (size) {
                             ^^^^

エラー文の中に、足りない分岐の名前('Loading'、'L')が出ています。 どこに何を足せばよいかが、エラー文だけでわかります。

エラー文 状況 直し方
Add an 'else' branch. Int や String など、列挙できない型 else -> を足す
Add the 'Loading' branch or an 'else' branch. sealed の型が1つ足りない Loading -> を足す
Add the 'L' branch or an 'else' branch. enum の定数が1つ足りない Size.L -> を足す
Add the 'M', 'L' branches or an 'else' branch. 複数足りない 足りない分岐をすべて足す

sealed と enum では else を書かないほうがよい理由

エラー文は「分岐を足すか、else を足すか」の2つを案内しています。sealed と enum の場合は、分岐を足すほうを選ぶのがおすすめです。理由を、型を1つ追加する流れで確かめます。

else を書いた場合:型を足しても気づけない

Result に Timeout という型をあとから追加したとします。when 側は else で受けています。

// sealed_else.kt
sealed interface Result
data class Success(val body: String) : Result
data class Failure(val code: Int) : Result
data object Loading : Result
data object Timeout : Result   // あとから追加した型

fun withElse(r: Result): String = when (r) {
    is Success -> "成功: ${r.body}"
    is Failure -> "エラー: ${r.code}"
    else -> "読み込み中"
}

fun main() {
    println(withElse(Timeout))
}

実行結果:

読み込み中

コンパイルは通り、Timeout なのに「読み込み中」と表示されました。else が新しい型も受け止めるため、対応が必要な場所をコンパイラが知らせてくれません。

else を書かない場合:コンパイラが場所を教えてくれる

同じ追加を、else なしの when に対して行います。

// sealed_noelse.kt
sealed interface Result
data class Success(val body: String) : Result
data class Failure(val code: Int) : Result
data object Loading : Result
data object Timeout : Result   // あとから追加した型

fun withoutElse(r: Result): String = when (r) {
    is Success -> "成功: ${r.body}"
    is Failure -> "エラー: ${r.code}"
    Loading -> "読み込み中"
}

fun main() {
    println(withoutElse(Timeout))
}

コンパイル結果:

sealed_noelse.kt:7:38: error: 'when' expression must be exhaustive. Add the 'Timeout' branch or an 'else' branch.
fun withoutElse(r: Result): String = when (r) {
                                     ^^^^

型を足した瞬間に、対応が必要な when がすべてコンパイルエラーとして一覧になります。 実行する前に直せるので、修正もれを防げます。

書き方 型を追加したとき 向いている場面
else を書く コンパイルが通り、else の結果になる Int・String など列挙できない型、または「その他はすべて同じ扱い」と決まっているとき
else を書かない コンパイルエラーで場所がわかる sealed・enum・Boolean で、種類ごとに扱いを決めたいとき

when を読みやすく書くチェックリスト

確認すること 理由
特別な条件・狭い範囲を上に書いたか 上から順に調べ、最初に当てはまった分岐だけが動くため
sealed・enum で else を使っていないか 型を足したときにコンパイラが場所を教えてくれるため
値を受け取るなら式として書いたか val s = when ... と書くと、代入もれが起きない
関数の戻り値で分岐するなら when (val x = ...) を使ったか 変数が when の外に残らない
複数の変数が関わるなら引数なしの when にしたか 条件式をそのまま並べられる
同じ型で条件が分かれるならガード条件を使ったか when の入れ子を減らせる
分岐が長くなったら関数に切り出したか -> の右が1行だと表のように読める

2つの値の組み合わせで分岐する

2つの値を同時に見たいときは、引数なしの when に条件を並べるのが素直です。x to y で Pair(2つの値の組)を作って比べる書き方もできます。

fun pair(x: Int, y: Int): String = when (x to y) {
    0 to 0 -> "原点"
    else -> when {
        x == 0 -> "Y軸上"
        y == 0 -> "X軸上"
        else -> "それ以外"
    }
}

fun main() {
    println("${pair(0, 0)} / ${pair(0, 3)} / ${pair(2, 0)} / ${pair(2, 3)}")
}

実行結果:

原点 / Y軸上 / X軸上 / それ以外

when の中に when を書くこともできますが、入れ子が深くなったら関数に分けると読みやすさを保てます。

よくある質問

Q. when と if はどう使い分けますか? A. 分岐が2つなら if、3つ以上なら when が読みやすくなります。どちらも式なので値を返せます。同じ値をいくつもの候補と比べるとき、範囲や型で分けるときは when が向いています。

Q. when で break や continue は必要ですか? A. 分岐を抜けるための break は不要です。当てはまった分岐だけが動き、次の分岐へは進みません。Java の従来の switch 文で break を書かなかった場合の「次の case へ進む」動きは、Kotlin の when にはありません。

Q. 「'when' expression must be exhaustive」と出たらどうすればよいですか? A. エラー文の「Add the '〇〇' branch」に書かれた分岐を足します。subject が sealed や enum のときは足りない名前が表示されるので、その分岐を書くのがおすすめです。Int や String のときは「Add an 'else' branch.」と出るので else を足します。

Q. ガード条件はどのバージョンから使えますか? A. Kotlin 公式の「What's new in Kotlin 2.2.0」に、2.1.0 でプレビュー、2.2.0 で Stable になったと書かれています。この記事では Kotlin 2.4.20 で、追加の設定なしにコンパイルできることを確かめました。

まとめ

  • when は「値・複数値・範囲(in)・型(is)・引数なし・subject変数・ガード条件」の7つの書き方を混ぜて使える
  • break は不要で、上から順に調べて最初に当てはまった分岐だけが動く
  • 式として使うときは網羅が必要。Int や String では else を書く
  • is で分岐すると右側でその型として扱える(スマートキャスト)
  • ガード条件(条件 if 追加条件)は Kotlin 2.2 以降。subject のある when で、カンマ区切りでない分岐に書ける
  • sealed・enum では else を書かず、全部の分岐を並べると、型を足したときにコンパイラが場所を教えてくれる
  • エラー文 'when' expression must be exhaustive には足りない分岐の名前が出るので、それを足せば解決する

参考・出典

  • Kotlin 公式ドキュメント「Conditions and loops」(when 式、ガード条件、網羅の決まり): https://kotlinlang.org/docs/control-flow.html
  • Kotlin 公式ドキュメント「What's new in Kotlin 2.2.0」(ガード条件の Stable 化): https://kotlinlang.org/docs/whatsnew22.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Kotlin when式の使い方|switchとの違い・型判定・sealed」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?