0Pricing
Kotlin Academy · レッスン

例外の再スローとチェーン

例外を選択的に再スローし、causeのチェーンで低レベルのエラーをラップします。

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

再スローとラップ

例外をログに記録したり、ラップしたり、変換したりしてから、より具体的な例外を伝播させるためだけに捕捉することがあります。Kotlinでは、これを慣用的に記述できます。

そのまま再スローする

再スローするには、throw eをもう一度実行するだけです。次のハンドラーへの伝播が続きます。

fun main() {
    try {
        try { "x".toInt() }
        catch (e: NumberFormatException) {
            println("logging: ${e.message}")
            throw e // re-throw original
        }
    } catch (e: NumberFormatException) {
        println("outer caught: ${e.message}")
    }
}

カスタム例外でラップする

新しい例外を構築するときに元の例外をcauseとして渡し、チェーンを保持します。

class ParseException(msg: String, cause: Throwable) : RuntimeException(msg, cause)
fun main() {
    try {
        try { "xyz".toInt() }
        catch (e: NumberFormatException) {
            throw ParseException("could not parse user input", e)
        }
    } catch (e: ParseException) {
        println("got: ${e.message}")
        println("cause: ${e.cause?.message}")
    }
}

原因チェーンを調べる

causeプロパティは、直前の例外へのリンクです。これをたどると、根本原因全体を出力できます。

fun main() {
    val e = RuntimeException("top",
        RuntimeException("middle",
            RuntimeException("root")))
    var current: Throwable? = e
    while (current != null) {
        println(current.message)
        current = current.cause
    }
}

選択的に再スローする

広い型を捕捉し、想定したケースに一致しない場合は再スローします。

fun parseRequired(s: String): Int {
    try {
        return s.toInt()
    } catch (e: NumberFormatException) {
        if (s.isBlank()) return 0
        throw e // unexpected — let it propagate
    }
}
fun main() {
    println(parseRequired("42"))
    println(parseRequired(""))
    // parseRequired("abc") // throws
}

コンテキストを追加する

ドメイン例外でラップすると、元の例外にはなかったコンテキスト(入力、リクエストIDなど)を追加できます。

class UploadError(msg: String, cause: Throwable) : RuntimeException(msg, cause)
fun upload(file: String) {
    try { throw RuntimeException("disk full") }
    catch (e: Throwable) { throw UploadError("uploading $file failed", e) }
}
fun main() {
    try { upload("photo.jpg") }
    catch (e: UploadError) { println("${e.message} <- ${e.cause?.message}") }
}

スタックトレースを失わない

ラップするときは、必ず元の例外をcauseとして渡してください。同じメッセージで原因のない新しい例外を作るだけではいけません。スタックトレースはデバッグの命綱です。

class Wrapped(m: String, cause: Throwable) : RuntimeException(m, cause)
fun main() {
    try {
        try { error("inner") }
        catch (e: Throwable) { throw Wrapped("outer", e) }
    } catch (e: Wrapped) {
        println("message: ${e.message}")
        println("cause: ${e.cause?.message}")
    }
}

ラップせずに再スローする

ログを記録するだけでよい場合は、ログ呼び出しの後にthrow eを実行します。ラップする必要はありません。

fun risky() {
    try { throw IllegalStateException("oops") }
    catch (e: IllegalStateException) {
        println("Logging before rethrow: ${e.message}")
        throw e
    }
}
fun main() {
    try { risky() }
    catch (e: IllegalStateException) { println("outer: ${e.message}") }
}

再スロー中のクリーンアップにfinallyを使う

再スローする場合でもリソースをクリーンアップできるよう、finallyを使用します。

fun process() {
    try {
        throw RuntimeException("failure")
    } finally {
        println("cleanup ran")
    }
}
fun main() {
    try { process() }
    catch (e: RuntimeException) { println("caught: ${e.message}") }
}

抑制された例外

finally自体が例外をスローすると、元の例外はデフォルトで抑制されます。addSuppressedを使用すると、両方の例外を保持できます。

fun main() {
    val primary = RuntimeException("primary")
    val secondary = RuntimeException("from cleanup")
    primary.addSuppressed(secondary)
    println(primary.suppressed.joinToString { it.message ?: "?" })
}

アンチパターン:握りつぶして別の例外をスローする

1つの例外を捕捉して、原因を指定せずに関連のない新しい例外を投げると、デバッグ情報が失われます。必ず cause を渡してください。

// BAD
// catch (e: IOException) { throw RuntimeException("error") }
// GOOD
// catch (e: IOException) { throw RuntimeException("error reading config", e) }
fun main() { println("Pass cause when re-throwing!") }

クイックチェック

例外を新しい例外でラップするとき、元のスタックトレースを保持するには何をすべきですか?

まとめ

再スローするには throw e を使用します。ラップするには、cause = e を指定して新しい例外を投げます。必ず原因チェーンを保持し、原因なしでラッパーを生成しないでください。クリーンアップには finally を使用し、クリーンアップ自体が失敗した場合は addSuppressed を使用します。

よくある質問

「例外の再スローとチェーン」レッスンは無料ですか?

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

「例外の再スローとチェーン」で何を学びますか?

例外を選択的に再スローし、causeのチェーンで低レベルのエラーをラップします。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「例外の再スローとチェーン」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

  1. 式としてのtry/catch/finally
  2. カスタム例外クラスの作成
  3. runCatchingとResult
  4. 例外の再スローとチェーン
← Kotlin Academyに戻る