例外って何者か(「普通じゃない事態」のシグナル)
例外は「プログラムが通常の流れでは処理できない異常事態が起きた」というシグナルです。 ファイルが見つからない、ネットワークが切れた、数値に変換できない文字列が来た、など「想定外だけど現実にはよく起きること」を、Javaは例外として表現します。
例外処理は、そのシグナルを受け止めて「落ちずに済ませる」「後始末をきちんとする」ための仕組みです。 ここをちゃんと設計できるかどうかが、システムの安定性と信頼性に直結します。
try / catch の基本構造と「落ちないコード」
try で「危ない処理」を囲う
例外が起きる可能性がある処理を、try ブロックで囲みます。
try {
int value = Integer.parseInt("123");
System.out.println("変換結果: " + value);
} catch (NumberFormatException e) {
System.out.println("数値に変換できませんでした");
}
JavaInteger.parseInt("123") は、文字列が数値として不正な場合に NumberFormatException を投げます。 try の中で例外が発生すると、そこで通常の処理は止まり、対応する catch にジャンプします。
「ここで何かトラブルが起きるかもしれない」と分かっている場所を、まず try で囲う。 それが例外処理のスタートラインです。
catch で「どう振る舞うか」を決める
catch は「この種類の例外が来たら、こう対応する」という場所です。
catch (NumberFormatException e) {
System.out.println("数値に変換できませんでした");
}
Javaここでやることは状況次第ですが、代表的なのは次のような振る舞いです。
エラーメッセージを出す ログに記録する ユーザーに再入力を促す 安全なデフォルト値に切り替える
重要なのは、「例外を握りつぶさない」「何が起きたか分かる形で残す」ことです。 catch の中を空にしてしまうと、「問題は起きているのに誰も気づかない」状態になり、後から原因調査が地獄になります。
複数の catch と例外の種類
例外クラスごとに分けて受け止める
Javaの例外はクラス階層になっていて、種類ごとに別のクラスになっています。 例えば、ファイル関連なら IOException、数値変換なら NumberFormatException、配列の範囲外なら ArrayIndexOutOfBoundsException などです。
1つの try に対して、複数の catch を並べることができます。
try {
String text = "123";
int value = Integer.parseInt(text);
int[] array = new int[2];
System.out.println(array[5]);
} catch (NumberFormatException e) {
System.out.println("数値変換エラー: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("配列の範囲外アクセス: " + e.getMessage());
}
Javaどの例外が投げられたかによって、対応を変えられるのがポイントです。 ビジネスロジックでは、「このエラーならユーザーにこう伝える」「このエラーならシステム管理者向けログだけ残す」といった細かい設計が重要になります。
例外の親子関係と catch の順番
例外クラスには親子関係があります。 例えば NumberFormatException は IllegalArgumentException の子クラスで、さらにその上に RuntimeException があります。
catch は上から順番に評価されるので、「より具体的な例外」を先に書き、「より抽象的な例外」を後ろに書くのが鉄則です。
try {
// 何か処理
} catch (NumberFormatException e) {
// 具体的な対応
} catch (RuntimeException e) {
// その他の実行時例外の対応
}
Javaもし先に親クラスを catch してしまうと、子クラスの catch に到達しなくなります。 「どの例外をどのレイヤで受け止めるか」「どこまで上に投げるか」は、APIレイヤ設計の重要なテーマです。
finally の役割(後始末を「必ず」やる場所)
finally は「例外があってもなくても」実行される
finally ブロックは、「例外が起きても起きなくても、最後に必ず実行される」場所です。
try {
System.out.println("処理開始");
int value = Integer.parseInt("123");
System.out.println("処理成功: " + value);
} catch (NumberFormatException e) {
System.out.println("処理失敗: " + e.getMessage());
} finally {
System.out.println("後始末処理を実行します");
}
Javaこのコードは、文字列が数値として正しくても、そうでなくても、最後に finally が必ず実行されます。 「後始末」「リソース解放」「接続のクローズ」など、「何があってもやらなきゃいけないこと」を書く場所が finally です。
リソース解放の典型パターン
例外処理で一番よく出てくるのが「ファイルやネットワーク接続などのリソースを必ず閉じる」パターンです。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FinallyExample {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
System.out.println("1行目: " + line);
} catch (IOException e) {
System.out.println("ファイル読み込みエラー: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
System.out.println("ファイルを閉じました");
} catch (IOException e) {
System.out.println("ファイルクローズ時のエラー: " + e.getMessage());
}
}
}
}
}
Javaここでは、読み込み中にエラーが起きても、起きなくても、最後に reader.close() を呼んでファイルを閉じています。 「開いたら必ず閉じる」「接続したら必ず切る」という後始末を、例外の有無に関係なく保証するのが finally の役割です。
最近の Java では try-with-resources という書き方で、リソース解放をもっと簡潔に書けますが、「finally で後始末を書く」という基本の考え方は変わりません。
例外を「握りつぶす」危険と、設計のポイント
何もせず catch するのはほぼバグ
初心者がやりがちな危険なパターンがこれです。
try {
int value = Integer.parseInt("abc");
} catch (NumberFormatException e) {
// 何もしない
}
Javaコンパイルエラーを消したくて、とりあえず try / catch を書いて、catch を空にしてしまう。 これは「問題が起きたことを完全に無視する」ことになり、後から原因が追えなくなります。
最低限、「ログを出す」「ユーザーに知らせる」「上位レイヤに投げ直す」など、何かしらの対応を入れるべきです。
catch (NumberFormatException e) {
System.err.println("数値変換に失敗しました: " + e.getMessage());
throw e; // そのまま上に投げる、という選択肢もある
}
Java「このレイヤで責任を持って処理するのか」「上位レイヤに判断を委ねるのか」を意識して、例外の扱いを設計することが大事です。
「落とさない」だけでなく「意味のある失敗」にする
例外処理の目的は「落とさない」だけではありません。 ビジネス的に意味のある失敗に変換することも重要です。
例えば、「外部API呼び出しが失敗したら、ユーザーには『現在サービスが混み合っています』と伝える」「ログには詳細なエラー内容を残す」といった設計です。
try {
callExternalService();
System.out.println("処理が正常に完了しました");
} catch (IOException e) {
System.out.println("現在サービスが混み合っています。しばらくしてから再度お試しください。");
logError(e); // 内部向けログ
}
Java「技術的な例外」を「ユーザーにとって意味のあるメッセージ」に変換する。 これが、APIレイヤやサービスレイヤでの例外処理設計の肝です。
まとめと小さな練習
例外処理は、システムの「安定性」と「後始末」を支える重要な仕組みです。 try で危ない処理を囲い、catch でどう振る舞うかを決め、finally で必ずやるべき後始末を書く。
練習として、次のようなコードを自分で書いてみると感覚がつかめます。
存在しないファイルを開こうとして IOException を catch し、ユーザー向けメッセージとログ出力を分ける 文字列の配列をループしながら Integer.parseInt を呼び、変換できないものだけエラーメッセージを出してスキップする
実際に「例外を起こしてみる」「catch でどう振る舞うかを自分で決める」「finally で後始末を書く」を体験すると、例外処理が「怖いもの」から「設計できる道具」に変わっていきます。
