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 Exception、RuntimeExceptionを継承するとUnchecked Exceptionになる。どちらを選ぶかは、エラーの性質によって決めよう。- 独自例外クラスには、エラーに関する追加情報を持たせると、さらに便利になるよ。
- 原因となった例外をラップすることで、デバッグがしやすくなるんだ。
独自例外クラスを使いこなせるようになると、あなたの書くコードはもっと分かりやすくて、堅牢になるはず。ぜひ積極的に使ってみてね!


コメント