Java入門|throwとthrowsによる例外の送出

例外を受け取るだけでなく、自分で送るしくみまでわかると、クラス設計がぐっと深くなる

これまでは、起きた例外を try-catch で受け取って処理する流れを見てきました。
ここまで理解できると、プログラムの実行中に起きた問題へ対応する基本はかなりつかめています。

ただ、Java の例外は「起きたものを受け取る」だけではありません。
実は、自分で作ったクラスの中から、意図的に例外を送ることもできます。
これが、throw と throws の役割です。

ドラゴンボールの世界でたとえると、仲間がたまたまトラブルに遭遇するだけでなく、
「この条件では修行を続けてはいけない」
「この装置設定では危険だから、ここで警告を出すべきだ」
と、設計した側がルールを決めておいて、異常な状態になったら正式に警報を出すようなイメージです。

つまり、throw と throws を使うと、クラスを作る側が
この状態は異常です
このメソッドでは例外が起こる可能性があります
と、はっきり示せるようになります。

この考え方は、自分一人のプログラムでも大切ですが、とくに他の人に使ってもらうクラスを設計するときにとても役立ちます。
ここでは、独自の例外クラスの作り方、throw による例外の送出、throws の意味、そして呼び出し側がどう対応するのかを、ドラゴンボールの世界観に置き換えながら丁寧に整理していきます。

例外は自分で送ることもできる

例外というと、配列の範囲外アクセスや数値変換の失敗のように、Java が自動的に起こすものを思い浮かべやすいです。
でも実際には、自分で「この状態はおかしい」と判断して例外を送出することもできます。

たとえば、あるクラスで

  • 負の値は受け付けたくない
  • 必須データがない場合は処理を続けたくない
  • 危険な設定値なら、その時点で知らせたい

といったルールを持たせたいことがあります。

そんなときに使うのが throw です。

ドラゴンボール風に言えば、
重力修行装置に危険な設定値が入ったら、そのまま修行を続けさせるのではなく、装置側が「この設定は危険です」と警告を発するようなものです。
その警告が、送出される例外です。

独自の例外クラスを作る

自分で例外を送るためには、まず例外クラスを用意します。
独自の例外クラスを作るには、Throwable 系列のクラスを継承する必要があります。

学習の基本としては、Exception を継承する形が分かりやすいです。

今回はドラゴンボール風の題材として、修行装置のエラーを表す TrainingException という例外クラスを作ることにします。

class TrainingException extends Exception
{
}

このクラスは、Exception を親にもつ独自の例外クラスです。
つまり、Java に対して
「TrainingException という例外の種類を新しく用意します」
と宣言していることになります。

ドラゴンボールの世界でたとえるなら、
通常の異常事態の大分類があって、その中に
「修行装置専用の危険警報」
という新しい警報の種類を追加したようなイメージです。

throw と throws の違いを先に整理しよう

throw と throws は名前が似ているので、最初は混ざりやすいです。
先に役割を表で整理しておくと、かなり分かりやすくなります。

書き方役割
throw実際に例外オブジェクトを送出する
throwsこのメソッドは例外を送出する可能性があると宣言する

つまり、

  • throw は実行時の動作
  • throws はメソッド宣言の情報

です。

ドラゴンボール風にすると、

  • throw は実際に警報ボタンを押すこと
  • throws は「この修行室では警報が鳴る可能性があります」と入口に注意書きを出すこと

に近いです。

throw は例外を実際に送る文

throw は、例外オブジェクトを実際に送出するときに使います。

基本の形は次の通りです。

throw 例外オブジェクト;

たとえば、

TrainingException e = new TrainingException();
throw e;

と書くと、TrainingException 型の例外オブジェクトを作り、それを送出できます。

もっと短く書くなら、次のようにもできます。

throw new TrainingException();

これで「この場面では TrainingException を発生させる」とはっきり示せます。

throws はメソッドが例外を送る可能性を示す

一方、throws はメソッド宣言の後ろに書きます。

public void setTraining(int level, double gravity) throws TrainingException

これは、
このメソッドは TrainingException を送出する可能性があります
という意味です。

ここで大事なのは、throws 自体が例外を起こしているわけではないことです。
あくまで、「このメソッドにはそういう可能性があります」と宣言しているだけです。

ドラゴンボールでたとえるなら、
「この修行装置は危険設定が入力された場合、警報が出る仕様です」
というルール説明にあたります。

サンプルプログラムで全体の流れを見てみよう

ここでは、独自の例外クラスを作り、メソッドの中で throw によって送出し、呼び出し側で catch するまでの流れを、1つのプログラムでまとめて見ていきます。

ファイル名:Sample5.java

class TrainingException extends Exception
{
}

// 修行装置クラス
class TrainingMachine
{
    private int level;
    private double gravity;

    public TrainingMachine()
    {
        level = 0;
        gravity = 0.0;
        System.out.println("修行装置を起動しました。");
    }

    public void setTraining(int l, double g) throws TrainingException
    {
        // 重力が負の値なら異常として例外を送出する
        if(g < 0)
        {
            TrainingException e = new TrainingException();
            throw e;
        }
        else
        {
            level = l;
            gravity = g;
            System.out.println("修行レベルを" + level + "に、重力を" + gravity + "に設定しました。");
        }
    }

    public void show()
    {
        System.out.println("現在の修行レベルは" + level + "です。");
        System.out.println("現在の重力設定は" + gravity + "です。");
    }
}

class Sample5
{
    public static void main(String[] args)
    {
        TrainingMachine machine1 = new TrainingMachine();

        try
        {
            machine1.setTraining(3, -50.0);
        }
        catch(TrainingException e)
        {
            System.out.println(e + " が送出されました。");
        }

        machine1.show();
    }
}

このプログラムで何をしているのか

このプログラムでは、まず TrainingException という独自の例外クラスを作っています。

class TrainingException extends Exception
{
}

次に、TrainingMachine クラスの中に setTraining メソッドを作っています。

public void setTraining(int l, double g) throws TrainingException

このメソッドは、修行レベルと重力設定を受け取るものです。
ただし、重力 g が 0 未満なら異常とみなし、例外を送出します。

if(g < 0)
{
    TrainingException e = new TrainingException();
    throw e;
}

つまり、負の重力設定は許可しない設計になっています。

もし g が正常な値なら、設定を保存してメッセージを表示します。

実行の流れを追ってみよう

main メソッドでは、まず修行装置を1台作成しています。

TrainingMachine machine1 = new TrainingMachine();

そのあとで、

machine1.setTraining(3, -50.0);

を呼び出しています。

ここでは重力に -50.0 を渡しているので、setTraining メソッドの中で条件に引っかかります。
その結果、TrainingException が作られて throw されます。

ただし main メソッド側では、その呼び出しを try-catch で囲んでいるので、送出された例外は catch で受け取られます。

catch(TrainingException e)
{
    System.out.println(e + " が送出されました。");
}

そのため、プログラムはそこで完全には止まらず、その後の show メソッドまで進みます。

実行結果を見てみよう

このプログラムを実行すると、流れは次のようになります。

修行装置を起動しました。
TrainingException が送出されました。
現在の修行レベルは0です。
現在の重力設定は0.0です。

ここで確認したいのは、例外が送出されたため、setTraining メソッドの正常処理は行われなかったことです。
つまり、level と gravity には新しい値が設定されていません。
そのため show メソッドでは、初期値のまま表示されています。

なぜ show の結果が初期値のままなのか

setTraining メソッドの中では、重力が負の値ならすぐに例外を送出しています。

if(g < 0)
{
    TrainingException e = new TrainingException();
    throw e;
}

この throw が実行されると、その時点でメソッドの通常の流れは中断されます。
そのため、その下にある代入処理や表示処理には進みません。

つまり、

  • level = l;
  • gravity = g;

といった設定は行われず、元のままになります。

ドラゴンボール風にいえば、
危険な重力設定が入力された瞬間に警報が鳴り、修行開始の処理が中断されたイメージです。
だから設定は反映されず、安全な初期状態のまま残ります。

throws を書く理由

setTraining メソッドには、次のように throws が書かれています。

public void setTraining(int l, double g) throws TrainingException

これは、呼び出し側に対して
このメソッドは TrainingException を送出する可能性があります
と知らせるためです。

つまり、このメソッドを使う人は、

  • 自分で try-catch して受け取る
  • あるいは、自分のメソッドにも throws を付けてさらに上へ渡す

のどちらかを考える必要があります。

これは、クラスを設計するうえでとても大事な考え方です。
メソッドを使う人に対して、どんな異常事態が起こりうるかを明示できるからです。

呼び出し側が選べる2つの対応

例外を送出する可能性があるメソッドを呼び出すとき、基本的な対応は2つあります。

対応内容
try-catch で処理するその場で例外を受け取って対処する
throws で呼び出し元へまかせるこのメソッドでは処理せず、さらに上へ渡す

今回の Sample5.java では、main メソッドの中で try-catch を使って処理しています。
つまり、呼び出したその場で受け止める方法を選んでいます。

この方法のよいところは、どこで例外に対応しているのかが分かりやすいことです。

もし catch しなかったらどうなるのか

ここでは具体的なコード例は増やしませんが、考え方としてとても重要なので整理しておきます。

もし main メソッドで try-catch を使わず、その代わりに main 側へ throws TrainingException を付けたとします。
すると、main 自身はその例外を処理しないことになります。

ところが main は、呼び出し元にさらに渡せる普通のメソッドとは少し立場が違います。
プログラムの入り口なので、その先に受け取る相手がいません。
そのため、最終的には例外が画面に表示され、プログラムが途中で終了します。

つまり、throws は
処理しなくてよい魔法の言葉
ではありません。
ここでは処理しないので、呼び出し元で対応してください
と伝えるためのものです。

throw と throws を混同しないコツ

この2つは本当に混ざりやすいので、覚え方をシンプルにしておくのがおすすめです。

キーワード覚え方
throwその場で例外を投げる
throwsこのメソッドは例外を投げるかもしれないと宣言する

語尾の s が付いている throws は、実際の動作というより「メソッドの説明書き」と考えると覚えやすいです。

図で全体の流れを整理する

この図は、独自の例外クラスを用意し、メソッドの中で throw によって例外を送出し、そのメソッドが throws で送出の可能性を宣言していることを表しています。
さらに、送出された例外が呼び出し側の catch へ渡る流れまで一続きで見えるので、throw と throws の違いが分かります。

この図から分かることは、主に次の通りです。

分かること内容
独自例外の作成Exception を継承して自分の例外クラスを作れる
throw の役割条件に応じて例外を実際に送出する
throws の役割メソッドが例外を送る可能性を宣言する
呼び出し側の責任try-catch するか、さらに throws するかを決める

図で例外が上へ渡る流れも整理できる

この図は、例外が発生したメソッドの中で処理されなければ、呼び出し元へ渡されることを示しています。
例外処理は「起きた場所で必ず終わる」とは限らず、メソッドの境界をまたいで上へ伝わっていく、という感覚がつかみやすくなります。

独自例外を作る意味

独自の例外クラスを作れるようになると、自分のクラスに対して
どんな異常を特別なものとして扱いたいか
を明確にできます。

たとえば今回なら、単なる数値エラーではなく、
「修行装置に不正な重力設定がされた」
という意味を持つ例外として TrainingException を作っています。

これによって、クラスを使う人は
「ああ、このクラスでは修行設定に関する問題が起こりうるんだな」
と理解しやすくなります。

つまり独自例外は、単なるエラー処理の道具ではなく、設計の意図を伝える名前付きの合図でもあります。

ここで押さえておきたいポイント

throw と throws を学ぶときは、次の点をしっかり押さえると整理しやすいです。

ポイント内容
独自例外クラスException を継承して作れる
throw例外オブジェクトを実際に送出する
throwsメソッドが例外を送出する可能性を宣言する
呼び出し側の選択try-catch するか、さらに throws するかを決める
例外送出の目的異常な状態をはっきり利用者へ知らせること

throw と throws を理解すると、例外処理は「起きた問題を受け止める技術」だけではなく、
クラス設計の中で異常をどう伝えるかを決める技術にもなります。

ドラゴンボールの世界でも、危険な装置に何の警報もなければ仲間は困ってしまいます。
でも、異常時に警報が出て、その警報を受けた側が対応できるしくみがあれば、安全に運用できます。
Java の throw と throws もまさにそれで、クラスを使う人に「ここには注意すべき異常がある」ときちんと伝えるためのしくみです。