Java道|throwとthrowsで安全な設計を作る

危険な状態を見つけたら、クラス自身が警告を出せる。
throw と throws を理解すると、例外処理は「受け取る技術」から「安全な設計を伝える技術」へ広がります。

これまで、例外処理では try-catch を使って、発生した例外を受け止める流れを学んできました。

たとえば、配列の範囲外アクセスが起きたとき、catch で ArrayIndexOutOfBoundsException を受け取り、分かりやすいメッセージを表示するような形です。

ここまで分かると、実行中に起きたトラブルへ対応する基本はかなり見えてきます。

しかし、Javaの例外は「発生したものを受け取る」だけではありません。
自分で作ったクラスの中から、意図的に例外を送出することもできます。

そのために使うのが throwthrows です。

鬼滅の刃風にたとえると、任務中にたまたまトラブルが起きるだけではありません。

修行装置を設計した記録係が、

危険な状態Javaでの考え方
重力設定がマイナスになっている不正な値なので例外を送出する
必須の隊士情報が登録されていない処理を続けず例外で知らせる
危険な修行レベルが指定されたクラス側で異常として扱う
呼び出し側に注意を促したいthrows で例外の可能性を宣言する

というように、あらかじめ安全ルールを決めておくイメージです。

つまり、throw と throws を使うと、クラスを作る側が、

この状態は異常です
このメソッドでは例外が起こる可能性があります

と、はっきり示せるようになります。

これは、自分だけが使う小さなプログラムでも大切です。
さらに、他の人に使ってもらうクラスを設計するときには、とても重要になります。

なぜなら、メソッドを呼び出す側が「この処理では、どんな異常に備えればよいのか」を理解しやすくなるからです。

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

例外というと、Javaが自動的に発生させるものを思い浮かべやすいです。

たとえば、次のような例です。

Javaが発生させる例外の例起きる状況
ArrayIndexOutOfBoundsException配列の範囲外へアクセスした
NumberFormatException数字に変換できない文字列を変換しようとした
NullPointerExceptionnull の参照先を使おうとした

このような例外は、実行中に不正な操作が起きたとき、Javaが知らせてくれます。

しかし、プログラムを作っていると、Javaの標準的なエラーだけではなく、自分の設計ルールとして異常を知らせたい場面 があります。

たとえば、鬼殺隊の修行装置を表すクラスを作るとします。

修行装置では、修行レベルと重力を設定できます。
しかし、重力がマイナスになるのはおかしいです。

重力:-50.0

このような値をそのまま受け入れてしまうと、修行装置の状態が不自然になります。

そこで、クラス側で

重力が0未満なら異常として知らせる

というルールを作ります。

このように、クラスの中で「この状態はおかしい」と判断し、例外を送るときに使うのが throw です。

鬼滅の刃風に言うと、修行装置に危険な設定値が入力された瞬間、装置自身が警告札を投げるようなものです。

「この重力設定では修行を開始できません」

と、修行を続ける前に正式に知らせるわけです。

独自の例外クラスを作る

自分で意味のある例外を送るには、独自の例外クラスを作れます。

独自の例外クラスを作るには、Throwable 系列のクラスを継承する必要があります。

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

今回は、鬼殺隊の修行装置で起きる異常を表す例外として、TrainingException を作ります。

class TrainingException extends Exception
{
}

このクラスは、Exception を継承しています。

つまり、Javaに対して、

TrainingException という独自の例外クラスを作ります

と宣言していることになります。

クラス役割
Exception例外処理の対象になる代表的な親クラス
TrainingException修行装置の異常を表す独自例外クラス

鬼滅の刃風にたとえると、TrainingException は「修行装置専用の警告札」です。

一般的な異常札である Exception の系統に属しながら、
「これは修行装置に関する異常です」
と名前で意味を伝えられるようになります。

独自例外を作る意味

独自例外を作る意味は、単にエラーを出すためだけではありません。

一番大切なのは、そのクラスでどんな異常が起こりうるのかを名前で伝えられること です。

たとえば、TrainingException という名前を見れば、

「修行設定や修行装置に関する問題が起きるのだな」

と分かります。

書き方伝わる意味
Exception何らかの一般的な例外
TrainingException修行装置に関する例外
FileNotFoundExceptionファイルが見つからない例外
ArrayIndexOutOfBoundsException配列の範囲外アクセスの例外

鬼滅の刃風に言えば、すべてを「異常札」と呼ぶだけでなく、
「修行装置警告札」
「名簿範囲外警告札」
「巻物紛失警告札」
のように、札の種類をはっきり分けるイメージです。

名前があることで、呼び出し側も対応を考えやすくなります。

throwとthrowsの違い

throw と throws は名前が似ているため、最初は混ざりやすいです。

ただし、役割はかなり違います。

キーワード役割書く場所鬼滅の刃風のイメージ
throw例外オブジェクトを実際に送出するメソッドの処理中警告札をその場で投げる
throws例外を送出する可能性があると宣言するメソッド宣言入口に注意書きを出す

つまり、throw は 実際の動作 です。

一方、throws は メソッドの説明 です。

たとえば、修行装置の中で危険な重力値を見つけた瞬間に警告札を投げるのが throw です。

そして、修行室の入口に
「この修行装置は危険な設定が入ると警告を出す可能性があります」
と書いておくのが throws です。

この違いを先に押さえておくと、あとでコードを読んだときに混乱しにくくなります。

throwは例外を実際に送る文

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

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

throw 例外オブジェクト;

たとえば、TrainingException オブジェクトを作って、それを送出するなら次のように書けます。

TrainingException e = new TrainingException();
throw e;

これは、次の2段階の処理です。

処理意味
new TrainingException()TrainingException オブジェクトを作る
throw e;その例外オブジェクトを送出する

もっと短く、次のようにも書けます。

throw new TrainingException();

この場合も意味は同じです。

TrainingException オブジェクトを作り、その場で送出しています。

鬼滅の刃風に言えば、修行装置が危険を検知した瞬間、TrainingException という警告札を作り、その場で投げるイメージです。

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

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

今回の例では、次のように書きます。

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

これは、

この setTraining メソッドは TrainingException を送出する可能性があります

という意味です。

ここで大切なのは、throws 自体が例外を発生させているわけではないことです。

throws は、メソッドを呼び出す側に対する注意書きです。

書き方意味
throw new TrainingException();実際に例外を送出する
throws TrainingExceptionこのメソッドはその例外を送出する可能性があると宣言する

鬼滅の刃風にたとえると、throws は修行室の入口に貼られた注意札です。

「この修行装置では、不正な重力設定が入ると TrainingException が出る可能性があります」

と、利用者に先に知らせています。

図:throwとthrowsの違い

↓クリックすると拡大表示されます。

この図が示していること

この図は、throw と throws の役割の違いを表しています。

throw は、条件に応じて例外オブジェクトを実際に送出する文です。
一方、throws は、メソッドがその例外を送出する可能性があることを呼び出し側へ知らせる宣言です。

図の要素意味
throw new TrainingException();実際に警告札を投げる
throws TrainingException警告が出る可能性を入口で知らせる
TrainingMachine危険値を判定する修行装置クラス
TrainingException修行装置の異常を表す独自例外

この図から分かることは、throw と throws は似た名前でも、実行時の動作とメソッド宣言という違う役割を持っているということです。

独自例外クラスで全体の流れを確認する

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

題材は、鬼殺隊の重力修行装置です。

修行装置では、修行レベルと重力を設定できます。
ただし、重力が0未満の場合は危険な設定として、TrainingException を送出します。

ファイル名: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();
    }
}

プログラムの登場人物

このプログラムには、3つのクラスが登場します。

クラス役割鬼滅の刃風のイメージ
TrainingException修行装置の異常を表す独自例外修行装置専用の警告札
TrainingMachine修行レベルと重力を管理するクラス鬼殺隊の重力修行装置
Sample5プログラムを実行するクラス修行装置を操作する任務指令

TrainingException は、Exception を継承した独自例外です。

class TrainingException extends Exception
{
}

TrainingMachine は、修行装置を表すクラスです。

修行レベルを表す level と、重力設定を表す gravity を持っています。

private int level;
private double gravity;

Sample5 は、main メソッドを持つ実行用のクラスです。

ここで TrainingMachine オブジェクトを作り、setTraining メソッドを呼び出します。

TrainingExceptionは独自の例外クラス

まず、TrainingException を見てみましょう。

class TrainingException extends Exception
{
}

このクラスは中身が空です。

それでも、Exception を継承しているため、例外クラスとして扱えます。

部分意味
class TrainingExceptionTrainingException というクラスを作る
extends ExceptionException を継承して例外として扱えるようにする
中身が空今回は独自の名前を持つ例外として使う

TrainingException という名前があることで、単なる一般的な例外ではなく、
「修行装置に関する異常」
として意味を持たせられます。

鬼滅の刃風に言えば、TrainingException は、鬼殺隊の修行装置だけが発行する特別な警告札です。

TrainingMachineクラスの役割

TrainingMachine クラスは、修行装置を表すクラスです。

class TrainingMachine
{
    private int level;
    private double gravity;
    ...
}

このクラスには、2つのフィールドがあります。

フィールド意味
level修行レベル
gravity重力設定

コンストラクタでは、初期値を設定しています。

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

初期状態では、level は 0、gravity は 0.0 です。

フィールド初期値
level0
gravity0.0

この状態から setTraining メソッドで修行内容を設定します。

setTrainingメソッドの宣言

setTraining メソッドは、次のように宣言されています。

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

このメソッドは、修行レベル l と重力 g を受け取ります。

そして、throws TrainingException によって、TrainingException を送出する可能性があることを示しています。

部分意味
public外部から呼び出せる
void戻り値はない
setTraining修行設定を行うメソッド
int l修行レベル
double g重力設定
throws TrainingExceptionTrainingException を送出する可能性がある

ここで大切なのは、throws TrainingException が「例外を実際に送っている」のではないことです。

このメソッドには、TrainingException が起きる可能性があります、と宣言しているだけです。

実際に例外を送るのは、メソッドの中にある throw です。

不正な重力値ならthrowする

setTraining メソッドの中では、まず重力 g が0未満かどうかを調べています。

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

g が0未満なら、負の重力設定です。

これは修行装置として不正な値なので、TrainingException を作って送出します。

処理を表にすると、次のようになります。

条件処理
g < 0TrainingException を作り throw する
g >= 0level と gravity に値を設定する

throw が実行されると、その時点で通常の処理は中断されます。

そのため、if の後にある正常設定の処理には進みません。

level = l;
gravity = g;
System.out.println("修行レベルを" + level + "に、重力を" + gravity + "に設定しました。");

鬼滅の刃風に言えば、修行装置に -50.0 という危険な重力値が入力された瞬間、警告札が投げられ、修行開始の処理が止まるイメージです。

正常な値なら設定する

一方、重力 g が0以上なら、正常な値として扱います。

else
{
    level = l;
    gravity = g;
    System.out.println("修行レベルを" + level + "に、重力を" + gravity + "に設定しました。");
}

この場合は、level と gravity に値を設定し、設定完了のメッセージを表示します。

入力値結果
setTraining(3, -50.0)例外を送出する
setTraining(3, 50.0)level と gravity を設定する

今回の Sample5.java では、setTraining(3, -50.0) を呼び出しています。

そのため、正常設定ではなく、例外送出の流れに入ります。

mainメソッド側の処理

main メソッドでは、まず TrainingMachine オブジェクトを作っています。

TrainingMachine machine1 = new TrainingMachine();

これにより、コンストラクタが実行され、初期値が設定されます。

次に、try の中で setTraining メソッドを呼び出しています。

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

setTraining は TrainingException を送出する可能性があるメソッドです。

そのため、呼び出し側では try-catch を使って、その例外に備えています。

今回、渡している重力は -50.0 です。

machine1.setTraining(3, -50.0);

これは不正な値なので、setTraining の中で TrainingException が送出されます。

送出された例外は、main メソッド側の catch で受け取られます。

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

実行結果

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

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

この流れを表で整理します。

出力どこで実行されたか
鬼殺隊の重力修行装置を起動しました。TrainingMachine のコンストラクタ
TrainingException が送出されました。catch
現在の修行レベルは0です。show
現在の重力設定は0.0です。show

ここで確認したいのは、例外が送出されたため、setTraining の正常処理が行われていないことです。

つまり、level と gravity には新しい値が設定されていません。

そのため、show メソッドでは初期値のまま表示されています。

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

show メソッドの結果が初期値のままなのは、setTraining の中で throw が実行されたからです。

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

throw が実行されると、その時点でメソッドの通常処理は中断されます。

そのため、次の代入処理には進みません。

level = l;
gravity = g;

つまり、setTraining(3, -50.0) と呼び出していても、level に 3 は入りません。
gravity に -50.0 も入りません。

項目結果
渡した level3
渡した gravity-50.0
実際の level0
実際の gravity0.0
理由throw により通常処理が中断されたため

鬼滅の刃風に言えば、危険な重力設定が入力された瞬間、修行装置が警告札を投げ、設定処理を止めた状態です。

そのため、修行装置は安全な初期状態のまま残っています。

図:Sample5.javaで例外が送出される流れ

↓クリックすると拡大表示されます。

この図が示していること

この図は、Sample5.java で TrainingException が作られ、throw によって送出され、main メソッド側の catch に渡る流れを表しています。

main メソッドから setTraining(3, -50.0) を呼び出します。
setTraining では、g < 0 の条件に当てはまるため、TrainingException オブジェクトを作って throw します。

throw が実行されると、setTraining の正常な設定処理は行われません。

その後、送出された TrainingException は、main メソッド側の catch で受け取られます。

図の要素意味
setTraining(3, -50.0)不正な重力値を渡している
throws TrainingExceptionこのメソッドは例外を送出する可能性がある
throw eTrainingException を実際に送出する
catch(TrainingException e)送出された例外を受け取る
show設定されていないため初期値を表示する

この図から分かることは、throw によってメソッドの通常処理が中断され、例外が呼び出し側へ渡るということです。

throwsを書く理由

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

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

これは、呼び出し側に対して、

このメソッドは TrainingException を送出する可能性があります

と知らせるためです。

この情報があることで、メソッドを使う側は次の対応を考えられます。

呼び出し側の対応内容
try-catch で処理するその場で例外を受け取って対応する
throws でさらに上へ渡す自分では処理せず、呼び出し元へ任せる

今回の Sample5.java では、main メソッドの中で try-catch を使って受け止めています。

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

この方法では、どこで例外を受け取っているのかが分かりやすいです。

鬼滅の刃風に言えば、修行装置が警告札を投げる可能性があることを事前に知っているため、支援隊士をその場に待機させている状態です。

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

throws が付いているメソッドを呼び出す側は、例外への対応を考える必要があります。

基本的な対応は2つです。

対応内容鬼滅の刃風のイメージ
try-catch で処理するその場で例外を受け取る現場の支援隊士が警告札を受け取る
throws でさらに上へ渡す自分では処理せず呼び出し元へ任せる本部の上位担当へ警告札を送る

Sample5.java では、try-catch で処理しています。

つまり、例外を呼び出したその場で受け止めています。

この形は、学習段階ではとても分かりやすいです。

方法特徴
try-catchその場で対応内容を書ける
throws処理を呼び出し元に任せられる

どちらが正しいというより、どこで対応するのが自然かを設計として考えます。

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

throws は、例外を消すための言葉ではありません。

ここがとても大切です。

throws は、

このメソッドでは処理しないので、呼び出し元で対応してください

と伝えるための宣言です。

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

ただし、main はプログラムの入口です。

その先に通常の呼び出し元があるわけではないため、最終的には例外情報が表示され、プログラムが途中で終了します。

状況結果
try-catch で受け取るその場で対応できる
throws で上へ渡す呼び出し元に対応を任せる
main まで処理されない最終的に例外が表示されて終了する

鬼滅の刃風に言うと、警告札を次の担当へ渡すことはできます。
しかし、最後の本部入口まで誰も受け取らなければ、任務は中断されます。

throws は「処理しなくてよい魔法の言葉」ではありません。
対応する場所を上位へ移すためのしくみです。

例外がメソッドの境界を越えて渡る

throw によって送出された例外は、発生したメソッド内で処理されない場合、呼び出し元へ渡されます。

今回の流れでは、TrainingMachine の setTraining メソッドで TrainingException が送出されます。

setTraining の中には catch がありません。

そのため、例外は setTraining を呼び出した main メソッド側へ渡されます。

setTraining
    ↓ TrainingException を送出
main の try-catch
    ↓ catch で受け取る

このように、例外はメソッドの境界を越えて伝わります。

鬼滅の刃風にたとえると、修行装置が出した警告札が、装置内部だけで処理されず、装置を操作している隊士のところへ飛んでくるようなものです。

図:例外が呼び出し元へ渡る流れ

↓クリックすると拡大表示されます。

この図が示していること

この図は、setTraining メソッドで送出された TrainingException が、呼び出し元である main メソッドへ渡る流れを表しています。

setTraining の中では、g < 0 の条件により TrainingException が送出されます。
しかし、setTraining の中では catch していません。

そのため、例外は呼び出し元である main メソッドの try-catch へ渡されます。

図の要素意味
setTraining例外が発生するメソッド
throw new TrainingException();例外を実際に送出する
throws TrainingException呼び出し元へ例外の可能性を知らせる
main の catch送出された例外を受け取る場所

この図から分かることは、例外処理は「起きた場所で必ず終わる」とは限らず、呼び出し元へ伝わっていくということです。

throwとthrowsを混同しないコツ

throw と throws は、見た目が似ています。

しかし、覚え方を分けると整理しやすいです。

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

throw は単数形で、実際に1つの例外オブジェクトを投げる文です。

throw e;

throws はメソッド宣言に付きます。

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

鬼滅の刃風にすると、次のように考えると分かりやすいです。

キーワード鬼滅の刃風
throw警告札を投げる
throws警告札が出る可能性を入口に書く

この2つを分けて理解すると、コードの流れが読みやすくなります。

安全な設計としてのthrowとthrows

throw と throws は、単にエラーを出すための道具ではありません。

クラス設計の中で、異常な状態をどう扱うかを決めるための重要なしくみです。

たとえば、TrainingMachine クラスは、負の重力値を受け入れない設計にしています。

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

このように書くことで、TrainingMachine は不正な状態になる前に処理を止められます。

さらに、setTraining メソッドには throws TrainingException が書かれています。

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

これにより、呼び出し側は「このメソッドでは TrainingException が起こる可能性がある」と分かります。

しくみ設計上の意味
独自例外クラスどんな異常かを名前で表す
throw異常な状態を見つけた時点で知らせる
throws呼び出し側へ異常の可能性を伝える
try-catch呼び出し側が対応を決める

鬼滅の刃風に言えば、修行装置は危険な設定を黙って受け入れません。

危険な設定が入ったら警告札を投げます。
さらに、装置の説明書には「危険設定では警告が出る可能性があります」と書いてあります。

だから、使う側は支援隊士を待機させたり、別の処理を準備したりできます。

独自例外を使うと意図が伝わりやすくなる

独自例外を作ると、プログラムの意味が読み取りやすくなります。

たとえば、単に Exception を使うよりも、TrainingException と書かれているほうが、何に関する異常なのかが分かりやすいです。

例外名読み取れる意味
Exception何らかの例外
TrainingException修行装置や修行設定に関する例外

この名前は、クラスを使う人へのメッセージになります。

「このクラスでは、修行設定に関する異常が起こる可能性があります」

と、コードから伝えられるわけです。

鬼滅の刃風にたとえると、警告札に「異常」とだけ書かれているより、
「重力修行装置エラー」
と書かれているほうが対応しやすいのと同じです。

throwとthrowsで押さえておきたいこと

throw と throws を学ぶときは、次の点を押さえると理解しやすくなります。

ポイント内容
独自例外クラスException を継承して作れる
throw例外オブジェクトを実際に送出する
throwsメソッドが例外を送出する可能性を宣言する
呼び出し側の対応try-catch するか、さらに throws するかを決める
throw 後の通常処理throw が実行されると、その後の通常処理は中断される
独自例外の意味異常の種類を名前で伝えられる
設計上の役割クラスの安全ルールを明確にできる

throw と throws を理解すると、例外処理は「起きた問題を受け止める技術」だけではなくなります。

クラスを作る側が、
「どんな状態を異常とするのか」
「その異常をどう呼び出し側へ伝えるのか」
を設計できるようになります。

鬼滅の刃風に言えば、危険な修行装置に何の警告もなければ、隊士は困ってしまいます。

しかし、危険値が入ったら警告札が出て、その警告を受ける側が対応できる仕組みがあれば、安全に運用できます。

Javaの throw と throws も同じです。

異常を見つけたら throw で知らせる。
その可能性を throws で宣言する。
呼び出し側は try-catch で受け止めるか、さらに上へ渡すかを決める。

この流れが分かると、例外処理はより実践的なクラス設計の道具として使えるようになります。