Java | 基礎文法:throws

Java Java
スポンサーリンク

throws の全体像

throws は「このメソッドは特定の例外を投げる可能性がある」と呼び出し側へ宣言するための構文です。主にチェック例外(IOException など)に使われ、呼び出し側に「捕まえるか、さらに投げるか」を選ばせます。設計の要点は「この層で回復しないなら、原因情報を保ったまま上位へ伝える」ことです。


基本構文と動き

メソッド宣言での throws

import java.io.*;
public class Reader {
    public static String firstLine(String path) throws IOException {
        try (var br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }
}
Java

この firstLine は入出力に失敗すると IOException を投げる可能性がある、と宣言しています。呼び出し側は try-catch で処理するか、さらに throws IOException を付けて上位へ伝播させます。

複数の例外を宣言

public static int loadPort(String env) throws NumberFormatException, IllegalStateException {
    String s = System.getenv(env);
    if (s == null) throw new IllegalStateException("環境変数がありません: " + env);
    return Integer.parseInt(s); // 不正なら NumberFormatException
}
Java

複数書けますが、実行時例外(RuntimeException系)は宣言しなくても構いません(宣言してもよい)。チェック例外は宣言か捕捉が必須です。


チェック例外と実行時例外(重要ポイントの深掘り)

どちらを throws するかの指針

チェック例外は「呼び出し側が現実的に回復できる」外部要因向け(I/O、ネットワーク、SQL)。この層で回復しない設計なら、具体例外をそのまま(または意味のある型へ包んで)throws で伝えます。実行時例外は契約違反やバグ向けで、通常は事前検証で防ぎ、throws 宣言は不要です。

層ごとの変換(例外の意味を合わせる)

インフラ層で SQLException が発生したら、アプリ層へは IllegalStateException やドメイン固有の例外に包み直すと、層ごとの責務が明確になります。包む際は原因例外(cause)を必ず渡し、スタックトレースを保ちます。

try {
    dao.save(order);
} catch (java.sql.SQLException e) {
    throw new IllegalStateException("注文保存に失敗: id=" + order.id(), e);
}
Java

伝播設計とエラーハンドリングの分担

「この層で回復しない」なら上位へ委ねる

アプリのトップレベル(UI層・HTTPハンドラなど)で一括ハンドリングすると、ログやユーザー通知が整理されます。中間層は「意味を付ける」「補足情報を足す」以外は無理に回復しないのが読みやすく、保守しやすい設計になります。

API の契約としての throws

throws はメソッド契約の一部です。「いつ・なぜ失敗するか」を JavaDoc に明記し、呼び出し側が正しく対処できるようにします。

/**
 * ファイルの先頭行を返す。
 * @param path 読み込むファイルのパス
 * @return 先頭行(null の可能性あり)
 * @throws IOException ファイルがない・読み込みに失敗した場合
 */
public static String firstLine(java.nio.file.Path path) throws java.io.IOException { ... }
Java

try-with-resources と throws の相性

try-with-resources は後始末(close)を自動化し、本体例外をそのまま throws できます。クローズ時の例外は「サプレスト例外」として本体に付属し、呼び出し側で getSuppressed() を確認できます。これにより「情報を失わず、適切な層で処理」がしやすくなります。

import java.nio.file.*;
import java.io.*;

static String read(Path p) throws IOException {
    try (var br = Files.newBufferedReader(p)) {
        return br.readLine();
    }
}
Java

よくある落とし穴(重要ポイントの深掘り)

throws で投げっぱなしにして握り潰す

上位で何もしないと「静かに失敗」します。トップレベルに到達した例外は、ログ・ユーザー通知・フォールバック(必要なら)を設計してください。「どこで責任を持つか」をチームで明文化するのが重要です。

catch で原因を捨てる包み直し

原因例外を渡さないと、根本原因が追えません。必ず new Xxx("msg", e) の形で原因を連鎖させます。

広すぎる throws 宣言

「throws Exception」は呼び出し側の負担を過大にします。実際に起こり得る型だけを宣言し、意味のある抽象へ束ねたい場合は自作の例外型(アプリ層の共通例外)へ包みます。

チェック例外を無理に実行時例外へ

安易な実行時例外への変換は「回復可能性」を失わせます。ユーザー入力や環境依存の失敗は、呼び出し側が選択できるようチェック例外を維持するか、結果オブジェクト(成功/失敗)で返す設計も検討します。


例題で身につける

例 1: 読み込みはこの層で回復せず、上位へ委ねる

import java.nio.file.*;
import java.io.*;

public class Loader {
    public static String firstLine(Path path) throws IOException {
        try (var br = Files.newBufferedReader(path)) {
            return br.readLine();
        }
    }
    public static void main(String[] args) {
        try {
            System.out.println(firstLine(Path.of("input.txt")));
        } catch (IOException e) {
            System.err.println("読込失敗: path=" + pathSafe(e));
            e.printStackTrace(); // 集約ハンドリング
        }
    }
    static String pathSafe(Exception e) { return e.getMessage(); }
}
Java

例 2: 低層の具体例外を意味のある型へ包んで再スロー

class Repository {
    User find(String id) {
        try {
            // DBアクセス...
            return new User(id, "Sato");
        } catch (java.sql.SQLException e) {
            throw new IllegalStateException("DB失敗: id=" + id, e); // 原因を連鎖
        }
    }
}
Java

例 3: 入力検証は事前に、異常は throws で伝える

public class Price {
    public static int taxed(int subtotal, double taxRate) {
        if (subtotal < 0) throw new IllegalArgumentException("subtotal must be >= 0");
        if (taxRate < 0 || taxRate > 1) throw new IllegalArgumentException("taxRate must be 0..1");
        return (int) Math.round(subtotal * (1 + taxRate));
    }
}
Java

契約違反は実行時例外で早期に弾き、外部要因の失敗はチェック例外として throws で上位へ。


仕上げのアドバイス(重要部分のまとめ)

throws は「このメソッドで回復しない失敗を、原因情報を保って上位へ渡す」宣言です。チェック例外は回復可能性のために維持し、実行時例外は契約違反の早期検出に使う。層に応じて意味のある型へ包み直し、原因を連鎖させる。トップレベルでの集約ハンドリングを設計し、try-with-resourcesで後始末を自動化して情報を失わない——この型が身につくと、例外設計はシンプルで強くなります。

タイトルとURLをコピーしました