【Java】例外処理についてまとめ
目次
- 例外処理の基本
- 例外クラスについて
- 対処方法
- try-catch文
- finally文
- try-with-resources
- throws宣言
- throw命令
例外処理の基本
- プログラム実行中に起きたエラーの対処
- Javaではエラーが発生すると例外オブジェクトが生成される
- その例外のオブジェクトに対処した処理を行うことが必要
JVM内が例外を発生するプログラムを検知し、throwでエラーを投げ込んでくる(自分で書いていない場合は暗黙的に)
その投げられた例外をプログラム内のどこかでキャッチして対処する必要がある
適切な処理をしないとプログラムは強制終了するので要注意
例外クラスについて
大前提としてすべての例外クラスは
Throwableクラス
を継承しています
その先で
- Errorクラス
- Exceptionクラス
に分岐していますね
ここで大事なことを言いますのでしっかりと理解して覚えて行ってください
Error系と例外系(Exceptionの方)はそもそも意味合いが全然違います
そして、Error系は自分が書くJavaプログラムで回復できない問題なので、これは自分たちが対処する範囲外です
これを念頭に置いて後続の内容に進んでいきましょう!!
対処方法
例外に対する処理の仕方は主に2パターンあります
- catchし、通常処理へ復帰させる
- catchせず、throwsにより呼び出し元に処理を委ねる
まずはcatchし、通常処理へ復帰させるについてサンプルコードを見ながら理解していきましょう
try-catch文
基本の構文はこちらです
try {
// 例外が発生する可能性のある処理
} catch (例外クラス 変数名) {
// 例外が発生した場合の処理
}
tryブロックの中で例外が発生すると、catchブロックに処理が飛びます
public class TryCatchSample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3};
try {
System.out.println(numbers[5]); // 存在しないインデックス
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("配列の範囲外にアクセスしました: " + e.getMessage());
}
System.out.println("プログラムは継続しています");
}
}
try-catchで囲まなかった場合、ArrayIndexOutOfBoundsExceptionが発生した時点でプログラムが強制終了します
catchしているからこそ、最後の「プログラムは継続しています」まで到達できるわけですね
そしてプログラムは正常終了します
もしこのmainメソッドの中身が別クラスに所属する別メソッドの実装内容だとして、
どこか別の場所から呼び出されていたとしても呼び出し元の後続のプログラムに続くことになります
catchを複数書く場合 〜順番に注意!〜
catchブロックは複数書けます
次のコードを見てみましょう
public class MultiCatchSample {
public static void main(String[] args) {
try {
String str = null;
System.out.println(str.length()); // NullPointerException発生
} catch (NullPointerException e) {
System.out.println("nullに対して操作しました");
} catch (RuntimeException e) {
System.out.println("実行時例外が発生しました");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
}
}
}
ここで非常に大事なことがあります
子クラス(具体的な例外)から順にcatchするということです
例えば以下のようにRuntimeExceptionを先に書いてしまうと…
// ★ダメな例 - コンパイルエラーになります
try {
String str = null;
System.out.println(str.length());
} catch (RuntimeException e) { // 先にこっちで全部catchされる
System.out.println("実行時例外");
} catch (NullPointerException e) { // ここに到達しない → コンパイルエラー
System.out.println("null操作");
}
NullPointerExceptionはRuntimeExceptionのサブクラスなので、親クラスのcatchが先にあると全部そこで捕まってしまいます
Javaコンパイラはこれを検知して「到達不能コード」としてコンパイルエラーにしてくれます
finally文
try {
// 例外が発生する可能性のある処理
} catch (Exception e) {
// 例外発生時の処理
} finally {
// 例外が発生してもしなくても必ず実行される処理
}
finallyは例外が起きても起きなくても必ず実行されるブロックです
典型的な使い道はリソースの解放(ファイルを閉じる、DB接続を切るなど)ですね
try-with-resources
最後にtry-with-resourcesという書き方について学びましょう
// try-with-resources
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("ファイル読み込みに失敗しました");
}
// tryブロックを抜けるとbrが自動的にclose()される
tryの()の中で宣言したリソースは、ブロックを抜ける際に自動でclose()が呼ばれます
条件として、そのクラスがAutoCloseableインターフェースを実装している必要があります
手動でcloseする必要がないので書き忘れも起きません
throws宣言
大前提として
- メソッド内でtry-catchを行わない場合、発生した例外は呼び出し元に投げられる
- throws宣言は「このメソッドはこの例外を投げる可能性がありますよ」とメソッド定義に明示するもの
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ThrowsSample {
// このメソッドではtry-catchしない → throwsで呼び出し元に処理を委ねる
public static String readFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
String line = br.readLine();
br.close();
return line;
}
public static void main(String[] args) {
try {
String content = readFile("test.txt");
System.out.println(content);
} catch (IOException e) {
System.out.println("ファイル読み込みに失敗しました: " + e.getMessage());
}
}
}
このreadFileメソッドではFileReaderやreadLine()がIOException(検査例外)を投げる可能性があります
ここで選択肢は2つあります
-
readFileメソッド内でtry-catchして自分で対処する - 自分では対処せず、呼び出し元に任せる
サンプルでは2を選んでいます
その場合、throws IOExceptionをメソッド定義に書かないとコンパイルエラーになります
検査例外なので「対処するか、投げるか、どっちかにしろ」とコンパイラに強制されるわけです
※非検査例外(RuntimeExceptionのサブクラス)の場合はthrows宣言を書かなくてもコンパイルエラーにはなりません
throw命令
throwは自分で明示的に例外を投げるための命令です
Javaが自動で投げてくれない例外を、自分の判断で投げたいときに使います
どういうときに使うかというと、業務ロジック的にありえない値や状態を検知したときです
例えば年齢にマイナスの値が入ってきた場合、Javaとしてはintに-5が入るだけなのでエラーにはなりません
でも業務的にはおかしいですよね
こういうときに自分でthrowして異常を知らせます
public class ThrowSample {
public static void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年齢に負の値は設定できません: " + age);
}
System.out.println("年齢を" + age + "に設定しました");
}
public static void main(String[] args) {
try {
setAge(-5);
} catch (IllegalArgumentException e) {
System.out.println("エラー: " + e.getMessage());
}
}
}
流れを整理すると
-
mainがsetAge(-5)を呼び出す -
setAgeメソッド内でage < 0を検知し、throwでIllegalArgumentExceptionを投げる - 呼び出し元の
mainのcatchブロックでキャッチして処理する
throwsとthrowの違い
ここまででthrowsとthrowが出てきたので整理しておきましょう
| throws | throw | |
|---|---|---|
| 役割 | このメソッドは例外を投げるかもしれないという宣言 | 今ここで実際に例外を投げる動作 |
| 書く場所 | メソッド定義のシグネチャ | メソッドの処理の中 |
| 書き方 | void method() throws IOException |
throw new IllegalArgumentException() |
※バリデーションの文脈で使えることを念頭に置いておきましょう
