独自例外クラスを作ってみよう!

docs

Javaでプログラミングをしていると、エラーに遭遇することは日常茶飯事。でも、Javaが用意してくれているExceptionクラスだけでは、ちょっと物足りないな……と感じることはありません。今回は、そんな時に役立つ独自例外クラスの作成方法を、とことん優しく解説していくよ。

なぜ独自例外クラスが必要なの?

前回の記事(例外処理の基本 try-catch | ToolDocs)で、JavaにはExceptionとかRuntimeExceptionとか、いろんな例外クラスがあるって話をしたよね。これらは、たとえば「ファイルが見つからない」とか「数字じゃないものを割り算しようとした」とか、Javaが想定している一般的なエラーを扱うのにすごく便利なんだ。

でも、考えてみて。もしあなたが作ったシステムで、「商品の在庫が足りない」とか「ユーザーがパスワードを3回間違えた」みたいな、あなたのアプリケーション固有の問題が発生した場合、どうする? NullPointerExceptionとかIOExceptionを使っても、何が起きたのかいまいち伝わらないよね。

そこで登場するのが、独自例外クラスなんだ。自分たちで専用のエラーメッセージや情報を盛り込んだ例外クラスを作れば、コードを読んだ人が一目で「何が起こったのか」「どうすればいいのか」を理解できるようになるよ。これは、コードの可読性を上げて、デバッグを楽にするためのすごく強力な武器なんだ。

独自例外クラスの作り方:基本のキ

独自例外クラスを作るのは、実はめちゃくちゃ簡単!基本的には、既存のExceptionクラスかRuntimeExceptionクラスを継承するだけなんだ。

「え、継承って難しそう……」って思った人もいるかもしれないけど、大丈夫。今は「元となる例外クラスの能力を受け継ぐ」ってくらいの理解でOKだよ。

Java

// 例:商品の在庫が足りないことを表す例外
public class OutOfStockException extends Exception {

    // コンストラクタ
    public OutOfStockException(String message) {
        super(message); // 親クラスのコンストラクタを呼び出す
    }
}

これで、OutOfStockExceptionという新しい例外クラスが誕生したよ!簡単でしょ?

super(message); っていうのは、親クラスであるExceptionクラスのコンストラクタ(初期化処理をする部分)を呼び出しているんだ。これで、例外が発生したときにメッセージを保持できるようになるよ。

Checked ExceptionとUnchecked Exception、どっちを継承する?

さて、ここで大事なポイント!どの例外クラスを継承するかによって、その例外がChecked ExceptionになるかUnchecked Exceptionになるかが決まるんだ。

  • Exceptionを継承した場合: これはChecked Exceptionになるよ。Checked Exceptionは、「この例外が発生する可能性があるよ」ってことを、throwsキーワードを使って明示的に宣言しないとコンパイルエラーになるんだ。つまり、コンパイラが「この例外、ちゃんと処理してる?」ってチェックしてくれるってこと。
    • 使うべき時: 回復可能なエラーや、ユーザーが入力した値の間違いなど、開発者が対処すべきエラーに使うのが一般的だよ。例えば、「ファイルが存在しない」とか「ネットワークに接続できない」など。
  • RuntimeExceptionを継承した場合: これはUnchecked Exceptionになるよ。Unchecked Exceptionは、throws宣言なしで例外を投げられるし、try-catchで捕まえなくてもコンパイルエラーにならないんだ。実行時に突然発生するようなエラーに近いイメージだね。
    • 使うべき時: プログラミングのミス(バグ)によって起こるエラーや、システムが回復できないような致命的なエラーに使うのが一般的。例えば、「NullPointerException」や「ArrayIndexOutOfBoundsException」などがこれにあたるよ。

どっちを使うべきか迷ったら、**「このエラーは、開発者が対処すべきものか?」**って考えてみて。もし「はい」ならException、もし「いいえ、これはバグだ」ならRuntimeExceptionを選ぶといいよ。

独自例外クラスを使ってみよう!

実際に作ったOutOfStockExceptionを使ってみよう!

Java

public class ProductService {

    private int stock = 10; // 仮の在庫数

    public void purchase(int quantity) throws OutOfStockException { // Checked Exceptionなのでthrows宣言が必要
        if (stock < quantity) {
            // 在庫が足りない場合に独自の例外を投げる
            throw new OutOfStockException("申し訳ありません。在庫が" + (quantity - stock) + "個不足しています。");
        }
        stock -= quantity;
        System.out.println(quantity + "個の商品を購入しました。残り在庫:" + stock + "個");
    }

    public static void main(String[] args) {
        ProductService service = new ProductService();

        try {
            service.purchase(5);  // 成功
            service.purchase(8);  // 在庫不足で例外発生
        } catch (OutOfStockException e) {
            System.err.println("エラーが発生しました: " + e.getMessage());
            // 必要に応じて、エラーに応じた処理を行う(例:ユーザーに別の商品を提案する)
        } finally {
            System.out.println("処理を終了します。");
        }
    }
}

このコードを実行すると、service.purchase(8)のところで在庫が足りなくなるので、OutOfStockExceptionが投げられ、catchブロックでエラーメッセージが表示されるはずだよ。

paizaで実行した結果

もっと独自例外クラスを便利に!

さっきのOutOfStockExceptionは、メッセージしか持たなかったよね。でも、独自例外クラスには、エラーに関するもっと詳しい情報を持たせることもできるんだ!

例えば、在庫不足の例外なら、何個足りなかったのかという情報も持たせると便利だよね。

Java

// 例:不足個数を持つOutOfStockException
public class OutOfStockException extends Exception {

    private int shortageQuantity; // 不足している個数

    public OutOfStockException(String message, int shortageQuantity) {
        super(message);
        this.shortageQuantity = shortageQuantity;
    }

    public int getShortageQuantity() {
        return shortageQuantity;
    }
}

そして、使う側ではこんな風に情報を取得できるよ。

Java

public class ProductServiceImproved {

    private int stock = 10;

    public void purchase(int quantity) throws OutOfStockException {
        if (stock < quantity) {
            int shortage = quantity - stock;
            throw new OutOfStockException("申し訳ありません。在庫が不足しています。", shortage);
        }
        stock -= quantity;
        System.out.println(quantity + "個の商品を購入しました。残り在庫:" + stock + "個");
    }

    public static void main(String[] args) {
        ProductServiceImproved service = new ProductServiceImproved();

        try {
            service.purchase(5);
            service.purchase(8);
        } catch (OutOfStockException e) {
            System.err.println("エラーが発生しました: " + e.getMessage());
            System.err.println("不足個数: " + e.getShortageQuantity() + "個"); // 不足個数を取得!
            // 不足個数に応じて、ユーザーに提案を変えるなどの処理が可能
        } finally {
            System.out.println("処理を終了します。");
        }
    }
}

これで、より詳細なエラーハンドリングができるようになったね!

paizaで実行した結果

ちょっと応用:原因となった例外をラップする

Javaの例外には、原因となった別の例外(cause)を持たせることができるんだ。これは、複数の処理をまたいで例外が発生した場合に、「この例外、もともとはこれが原因だったんだよ」って教えてくれる機能だよ。

たとえば、ファイルから商品データを読み込もうとしたら、ファイルが壊れていてIOExceptionが発生したとする。でも、その結果として「商品が登録できなかった」というビジネスロジック上の例外(例:ProductRegistrationException)を投げたい場合。こんな風にできるよ。

Java

import java.io.IOException;

// 商品登録に関する独自の例外クラス
public class ProductRegistrationException extends Exception {
    public ProductRegistrationException(String message, Throwable cause) {
        super(message, cause); // 原因となった例外を渡す
    }
}

public class DataImporter {

    public void importProductsFromFile(String filePath) throws ProductRegistrationException {
        try {
            // ここでファイル読み込み処理(仮)
            // 実際はファイルの存在チェックやフォーマット検証など
            if (filePath.contains("error")) { // 例としてエラーを発生させる
                throw new IOException("ファイルが破損しています: " + filePath);
            }
            System.out.println(filePath + "から商品をインポートしました。");
        } catch (IOException e) {
            // IOExceptionが発生したら、それを原因として新しいProductRegistrationExceptionを投げる
            throw new ProductRegistrationException("商品データのインポート中にエラーが発生しました。", e);
        }
    }

    public static void main(String[] args) {
        DataImporter importer = new DataImporter();
        try {
            importer.importProductsFromFile("product_data_error.txt"); // エラーを発生させる
        } catch (ProductRegistrationException e) {
            System.err.println("商品登録エラー: " + e.getMessage());
            // 原因となった例外のスタックトレースも表示できる
            System.err.println("原因: " + e.getCause().getMessage());
            e.printStackTrace(); // 原因の例外も含めたスタックトレース
        }
    }
}

こうすることで、エラーの原因を遡って調べられるようになるから、デバッグがさらに楽になるよ!

paizaで実行した結果

まとめ

今回は、Javaで独自例外クラスを作る方法について解説したよ。

  • 独自例外クラスは、アプリケーション固有のエラーを明確に表現するために作るんだ。
  • Exceptionを継承するとChecked ExceptionRuntimeExceptionを継承するとUnchecked Exceptionになる。どちらを選ぶかは、エラーの性質によって決めよう。
  • 独自例外クラスには、エラーに関する追加情報を持たせると、さらに便利になるよ。
  • 原因となった例外をラップすることで、デバッグがしやすくなるんだ。

独自例外クラスを使いこなせるようになると、あなたの書くコードはもっと分かりやすくて、堅牢になるはず。ぜひ積極的に使ってみてね!

これまでの記事もチェック!

finallyブロック:例外があってもなくても動く処理 | ToolDocs

コメント

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