0Pricing
Kotlin Academy · レッスン

sealed階層を網羅するwhen

すべてのsealedバリアントを網羅する完全なwhen式を記述します。

「sealed階層を網羅するwhen」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。

網羅的な when

式として使用する when 式は網羅的である必要があります。つまり、考えられるすべての値を処理しなければなりません。sealed 階層を使うと、コンパイラーがこれを検証してくれます。

オープンな型での非網羅的な when

通常のクラスでは、コンパイラーはすべてのサブタイプを確認できないため、else ブランチが必要です。

open class Shape
class Circle : Shape()
class Square : Shape()
fun describe(s: Shape) = when (s) {
    is Circle -> "circle"
    is Square -> "square"
    else      -> "unknown" // required
}
fun main() { println(describe(Circle())) }

sealed 型での網羅的な when

sealed class では、コンパイラーがすべてのサブタイプを把握しています。すべてのケースを網羅していれば、else は不要です。

sealed class Result
class Success(val value: Int) : Result()
class Failure(val msg: String) : Result()
fun describe(r: Result) = when (r) {
    is Success -> "ok: ${r.value}"
    is Failure -> "err: ${r.msg}"
}
fun main() {
    println(describe(Success(42)))
    println(describe(Failure("nope")))
}

sealed interface

sealed interface を使うと、クラスが多重継承を必要とする場合でも網羅性を検証できます。

sealed interface Animal
class Dog : Animal
class Cat : Animal
fun speak(a: Animal) = when (a) {
    is Dog -> "woof"
    is Cat -> "meow"
}
fun main() {
    println(speak(Dog()))
    println(speak(Cat()))
}

ブランチでのスマートキャスト

各 is ブランチ内では、Kotlin が変数を特定の型にスマートキャストするため、手動でキャストする必要はありません。

sealed class Event
data class Click(val x: Int, val y: Int) : Event()
data class Key(val char: Char) : Event()
fun handle(e: Event) = when (e) {
    is Click -> "clicked at (${e.x},${e.y})" // smart-cast
    is Key   -> "key=${e.char}"
}
fun main() {
    println(handle(Click(10, 20)))
    println(handle(Key('A')))
}

ケースの記述漏れ

後から新しいバリアントを追加すると、sealed 型を式として使用しているすべての網羅的な when がコンパイルに失敗します。これは非常に有効な安全策です。

sealed class Status
object Idle : Status()
object Running : Status()
// If we add object Done : Status(), every exhaustive when must update.
fun text(s: Status): String = when (s) {
    is Idle -> "idle"
    is Running -> "running"
}
fun main() { println(text(Idle)) }

data class を使った sealed 型

よく使われるパターンは、sealed 階層のバリアントとして data class を使用することです。

sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<String>) : UiState()
    data class Error(val message: String) : UiState()
}
fun render(state: UiState) = when (state) {
    UiState.Loading -> "loading..."
    is UiState.Success -> "got ${state.data.size} items"
    is UiState.Error -> "error: ${state.message}"
}
fun main() {
    println(render(UiState.Loading))
    println(render(UiState.Success(listOf("a", "b"))))
    println(render(UiState.Error("404")))
}

when をステートメントとして使う

ステートメントとして使用する場合(式ではない場合)、when は網羅的である必要がありません。ただし、安全策を失うことになります。

sealed class Cmd
object Start : Cmd()
object Stop : Cmd()
fun execute(c: Cmd) {
    // Statement form: no error if incomplete (avoid this style)
    when (c) {
        Start -> println("started")
        Stop -> println("stopped")
    }
}
fun main() { execute(Start); execute(Stop) }

ステートメントで網羅性チェックを強制する

when を値に代入するか、.exhaustive のテクニックを使って、コンパイラーにすべてのケースを検証させます。

sealed class Cmd
object Start : Cmd()
object Stop : Cmd()
fun execute(c: Cmd) {
    val handled: Unit = when (c) { // expression context now
        Start -> println("started")
        Stop -> println("stopped")
    }
}
fun main() { execute(Start) }

複数ケースのブランチ

カンマでケースをまとめると、複数のバリアントで同じ本体を共有できます。

sealed class Event
class Click : Event()
class Tap : Event()
class Drag : Event()
fun isPointer(e: Event) = when (e) {
    is Click, is Tap, is Drag -> true
}
fun main() { println(isPointer(Tap())) }

ネストされた sealed 型

sealed 階層はネストできます。深いツリーに対しても網羅性チェックは機能します。

sealed class Network {
    sealed class Wifi : Network() {
        object Open : Wifi()
        data class Secured(val ssid: String) : Wifi()
    }
    object Cellular : Network()
}
fun describe(n: Network) = when (n) {
    Network.Wifi.Open -> "open wifi"
    is Network.Wifi.Secured -> "secured: ${n.ssid}"
    Network.Cellular -> "cellular"
}
fun main() {
    println(describe(Network.Wifi.Secured("home")))
    println(describe(Network.Cellular))
}

クイックチェック

sealed 階層と when を組み合わせる主な利点は何ですか?

まとめ

sealed class や sealed interface と when を組み合わせて、コンパイル時の網羅性を確保します。スマートキャストによってブランチ本体を簡潔に記述できます。式として使用するか、結果を代入してチェックを強制し、カンマでケースをまとめ、ツリー状の状態には sealed 型をネストしてください。

よくある質問

「sealed階層を網羅するwhen」レッスンは無料ですか?

はい。「sealed階層を網羅するwhen」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。

「sealed階層を網羅するwhen」で何を学びますか?

すべてのsealedバリアントを網羅する完全なwhen式を記述します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Kotlin Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「sealed階層を網羅するwhen」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このKotlin Academyレッスンでコードを書いて実行できますか?

はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. sealed classとsealed interface:使い分け
  2. sealed階層を網羅するwhen
  3. Sealed ClassによるUI状態のモデル化
  4. Sealed階層のネストと組み合わせ
← Kotlin Academyに戻る