Java:例外処理
1学習の目的
- try-catch でエラーを捕まえ、プログラムを止めずに対処できるようになる。想定外の入力やファイル不在に備えられるようになる。
- 自作の例外クラスを作れるようになる。何が起きたかを正確に伝えるエラー設計ができるようになる。
2基礎解説
| ブロック | 役割 | 実行されるタイミング |
|---|---|---|
| try | 失敗するかもしれない処理 | 常に |
| catch | エラーが起きたときの対処 | エラー時のみ |
| finally | 後始末 | 必ず |
| throw | エラーを発生させる | 明示的に |
- try の中でエラーが起きると、その先は飛ばして catch へ移る。プログラム全体が止まるのを防げる。
- finally は必ず実行される。return しても、例外が起きても実行されるので、ファイルを閉じるなどの後始末に使う。
- catch は具体的なものから先に書く。Exception(すべての例外の親)を先に書くと、それ以降が到達不能になりコンパイルエラーになる。
- 検査例外は throws の宣言が必要。Exception を継承した例外は、メソッドに throws を書かないとコンパイルが通らない——呼び出す側に対処を強制できる。
- ⚠ catch を空にしない。catch (Exception e) { } と書くと、エラーが起きても何も分からなくなる。最低でもメッセージは出力する。
現場使用例:ファイルが見つからない、数値変換の失敗、ネットワークの切断、在庫不足などの業務エラー。
// ① 基本のtry-catch
try {
int result = 10 / 0; // ここでエラー
System.out.println("ここは実行されない");
} catch (ArithmeticException e) {
System.out.println(e.getMessage()); // "/ by zero"
}
// ② 複数のcatch(具体的なものから先に)
try {
Integer.parseInt("abc");
} catch (NumberFormatException e) { // 具体的
System.out.println("数値ではありません");
} catch (Exception e) { // 一般的(後ろ)
System.out.println("その他のエラー");
}
// ③ finally は必ず実行される
try {
return "値"; // return しても…
} finally {
System.out.println("これは実行される");
}
// ④ 複数の型をまとめて捕まえる
try { … }
catch (IllegalArgumentException | IllegalStateException e) { … }
// ⑤ よく出会う例外
// ArithmeticException … 0で割った
// NullPointerException … null に対する操作
// NumberFormatException … 数値変換の失敗
// ArrayIndexOutOfBoundsException … 配列の範囲外
// ⑥ 自作の例外(業務エラーを表現する)
static class OutOfStockException extends Exception {
private int remaining;
public OutOfStockException(String msg, int remaining) {
super(msg);
this.remaining = remaining; // 追加情報を持てる
}
public int getRemaining() { return remaining; }
}
// throws で「失敗する可能性」を宣言する
static void order(int stock, int qty) throws OutOfStockException {
if (qty > stock) {
throw new OutOfStockException("在庫不足です", stock);
}
}
// 呼び出す側は対処を強制される
try {
order(5, 100);
} catch (OutOfStockException e) {
System.out.println(e.getMessage() + " 残り" + e.getRemaining());
}
3基本ドリル(10問)
出力 == / by zero
ArithmeticException を catch し、e.getMessage() を出力する。
public class Main {
public static void main(String[] args) {
try {
int result = 10 / 0;
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
}
}エラーが起きても処理が続く。catch が無ければ、この時点でプログラムが停止していた。
A. エラー時のみ B. 正常時のみ C. 必ず実行される D. 実行されない 選択★☆☆無料
解答 == C
後始末のためのブロック。
C
return しても、例外が起きても実行される。ファイルを閉じる・接続を切るなどの後始末に使う。
出力 == 数値に変換できません
Integer.parseInt は NumberFormatException を投げる。
public class Main {
public static void main(String[] args) {
try {
int n = Integer.parseInt("abc");
System.out.println(n);
} catch (NumberFormatException e) {
System.out.println("数値に変換できません");
}
}
}入力値の変換は必ず失敗を想定する。ユーザーが何を入力するかは分からないので、try-catch で守るのが基本。
public class Main {
public static void main(String[] args) {
try {
int[] a = new int[2];
System.out.println(a[5]);
} ____ (ArrayIndexOutOfBoundsException e) {
System.out.println("範囲外です");
}
}
}出力 == 範囲外です
「捕まえる」を意味する5文字のキーワード。
catch
catch で例外の種類を指定して受け取る。指定した型と一致する例外だけが捕まえられる。
A. Exception を最初に B. 具体的な例外を最初に C. 順序は自由 D. アルファベット順 選択★☆☆無料
解答 == B
Exception はすべての例外の親。
B
Exception を先に書くと、後ろの catch が到達不能になってコンパイルエラー。具体的なものから順に並べる。
出力2行。処理完了 / 後始末
try の中で出力し、finally でも出力する。
public class Main {
public static void main(String[] args) {
try {
System.out.println("処理完了");
} catch (Exception e) {
System.out.println("エラー");
} finally {
System.out.println("後始末");
}
}
}エラーが起きなくても finally は実行される。「成功しても失敗しても必ずやること」を書く場所。
public class Main {
public static void main(String[] args) {
try {
____ new IllegalArgumentException("不正な値");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}出力 == 不正な値
「投げる」を意味する5文字。
throw
throw で意図的に例外を発生させる。業務ルールに違反したときなど、処理を続けるべきでない場面で使う。
A. 遅くなる B. エラーが起きても気づけない C. コンパイルエラー D. メモリを使う 選択★★☆広告解放
解答 == B
何も出力しないとどうなるか。
B
エラーが握りつぶされる。動かない原因が分からず、調査に何時間もかかる——実務で最も嫌われる書き方の1つ。
出力 == 在庫不足です 残り5
class OutOfStockException extends Exception { ... } でフィールドとgetterを持たせる。
public class Main {
static class OutOfStockException extends Exception {
private int remaining;
public OutOfStockException(String msg, int remaining) {
super(msg);
this.remaining = remaining;
}
public int getRemaining() {
return remaining;
}
}
static void order(int stock, int qty) throws OutOfStockException {
if (qty > stock) {
throw new OutOfStockException("在庫不足です", stock);
}
}
public static void main(String[] args) {
try {
order(5, 100);
} catch (OutOfStockException e) {
System.out.println(e.getMessage() + " 残り" + e.getRemaining());
}
}
}例外に追加情報を持たせられる。「在庫不足」というメッセージだけでなく、「残り何個か」まで呼び出し元に伝えられる。
出力 == 状態エラー
catch (A | B e) の形で複数の型を並べられる。
public class Main {
public static void main(String[] args) {
try {
throw new IllegalStateException("状態エラー");
} catch (IllegalArgumentException | IllegalStateException e) {
System.out.println(e.getMessage());
}
}
}同じ対処をする例外はまとめて書ける。catch を何個も並べて同じ処理を繰り返す必要がない。
4実践シナリオ(5問)
出力 == 131200
ループの中で try-catch し、失敗したら continue する。
public class Main {
public static void main(String[] args) {
String[] inputs = {"128000", "abc", "3200"};
int total = 0;
for (String s : inputs) {
try {
total += Integer.parseInt(s);
} catch (NumberFormatException e) {
continue;
}
}
System.out.println(total);
}
}1件失敗しても全体を止めない。CSVの取り込みなど、一部に不正データが混ざる場面で有効な書き方。
出力 == 計算エラー
具体的な例外から順に catch を並べる。
public class Main {
public static void main(String[] args) {
try {
int n = Integer.parseInt("10");
System.out.println(n / 0);
} catch (NumberFormatException e) {
System.out.println("変換エラー");
} catch (ArithmeticException e) {
System.out.println("計算エラー");
}
}
}エラーの種類ごとに対処を変えられる。変換の失敗なら再入力を促し、計算エラーなら処理を見直す——原因に応じた対応ができる。
出力3行
try で開いてエラーを起こし、finally で閉じる。
public class Main {
public static void main(String[] args) {
try {
System.out.println("開く");
throw new RuntimeException("失敗");
} catch (RuntimeException e) {
System.out.println("エラー発生");
} finally {
System.out.println("閉じる");
}
}
}エラーが起きても必ず閉じられる。ファイルや接続を開いたまま放置すると、リソースが枯渇してシステム全体が止まる。
出力 == 価格が不正です:-100
例外に不正だった値を持たせ、getter で取り出す。
public class Main {
static class InvalidPriceException extends Exception {
private int value;
public InvalidPriceException(int value) {
super("価格が不正です");
this.value = value;
}
public int getValue() {
return value;
}
}
static void setPrice(int price) throws InvalidPriceException {
if (price < 0) {
throw new InvalidPriceException(price);
}
}
public static void main(String[] args) {
try {
setPrice(-100);
} catch (InvalidPriceException e) {
System.out.println(e.getMessage() + ":" + e.getValue());
}
}
}何が不正だったかを例外が持っている。ログに残すときも、ユーザーに伝えるときも、具体的な値があると原因究明が早い。
出力 == 注文できません
static void order(...) throws Exception の形で宣言する。
public class Main {
static class OrderException extends Exception {
public OrderException(String msg) {
super(msg);
}
}
static void order(int stock, int qty) throws OrderException {
if (qty > stock) {
throw new OrderException("注文できません");
}
System.out.println("注文成功");
}
public static void main(String[] args) {
try {
order(3, 10);
} catch (OrderException e) {
System.out.println(e.getMessage());
}
}
}throws を書くと、呼び出す側は必ず対処を求められる。try-catch を書かなければコンパイルが通らないので、エラーの見落としが起きない。
5仕上げ課題
自作例外 OrderException(Exception を継承)
・メッセージとエラーコード(String)を持つ
・getCode() でコードを取得
メソッド processOrder(String qtyInput, int stock, int price)(throws OrderException)
① qtyInput を数値に変換(失敗なら コード E01「数量が数値ではありません」)
② 数量が0以下なら コード E02「数量が不正です」
③ 数量が在庫を超えたら コード E03「在庫不足です」
④ 成功なら合計金額を返す(int)
main の処理:4件の注文を順に処理し、結果を出力(finally で毎回「---」を出力)
・"2", 在庫5, 単価128000 → 成功
・"abc", 在庫5, 単価128000 → E01
・"0", 在庫5, 単価128000 → E02
・"100", 在庫5, 単価128000 → E03
期待される出力(8行)
成功:256,000円
---
[E01] 数量が数値ではありません
---
[E02] 数量が不正です
---
[E03] 在庫不足です
--- 総合★★★広告解放
出力8行が完全一致
parseInt の失敗は NumberFormatException を catch して、OrderException に変換して投げ直す。ループの中で try-catch-finally を書けば、毎回 --- が出力される。
public class Main {
static class OrderException extends Exception {
private String code;
public OrderException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() {
return code;
}
}
static int processOrder(String qtyInput, int stock, int price)
throws OrderException {
int qty;
try {
qty = Integer.parseInt(qtyInput);
} catch (NumberFormatException e) {
throw new OrderException("E01", "数量が数値ではありません");
}
if (qty <= 0) {
throw new OrderException("E02", "数量が不正です");
}
if (qty > stock) {
throw new OrderException("E03", "在庫不足です");
}
return price * qty;
}
public static void main(String[] args) {
String[] inputs = {"2", "abc", "0", "100"};
for (String input : inputs) {
try {
int total = processOrder(input, 5, 128000);
System.out.println("成功:" + String.format("%,d", total) + "円");
} catch (OrderException e) {
System.out.println("[" + e.getCode() + "] " + e.getMessage());
} finally {
System.out.println("---");
}
}
}
}4件の注文のうち3件が失敗したが、プログラムは最後まで動き切った。これが例外処理の価値だ。1件の失敗が全体を止めない——バッチ処理や一括取り込みでは決定的に重要になる。
注目してほしいのは NumberFormatException を捕まえて OrderException に投げ直している点だ。parseInt が投げる例外は「Javaの技術的なエラー」だが、呼び出し元が知りたいのは「注文処理のどこで失敗したか」である。技術的な例外を業務的な例外に変換する——これは実務で頻繁に使う設計手法だ。
エラーコード(E01・E02・E03)を持たせたのにも理由がある。メッセージは人間が読むもの、コードは機械が判定するものだ。ログを集計するとき「E03が今月50件」と数えられるし、多言語対応するときもコードは変わらない。
finally がループの各回で実行される点も確認できた。成功しても失敗しても必ず区切り線が出る——ファイルを閉じる、接続を切る、ログを書き出すといった後始末は、この場所に書く。
次章ではファイル入出力を学ぶ。例外処理が必須になる領域であり、今回学んだことが直接役立つ。