Skip to content
Playground

演習: 例外処理

各問題ごとに .java ファイルを作り、Main.java で動作確認します。

9-A. ゼロ除算のスタックトレースを読む

Section titled “9-A. ゼロ除算のスタックトレースを読む”

整数を 0 で割ると、ArithmeticException が発生します。次のコードを Main.javamain メソッドに書いて実行してください。例外が発生して異常終了するはずです。

Main.java
int total = 100;
int count = 0;
int average = total / count;
System.out.println("平均: " + average);

表示されるスタックトレースから、例外の型・メッセージ・発生場所の 3 つをそれぞれ読み取り、何が起きたかを説明してください。

ヒント

スタックトレースの最初の 1 行が例外の型とメッセージです。java.lang.ArithmeticException: / by zero のように表示され、/ by zero(0 で割った)がメッセージです。at で始まる行が発生場所で、average を計算した行を指します。count が 0 のため割り算ができず、例外が発生しています。

割り算を行う Calculator クラスを作ってください。次のとおり定義します。

  • divide(int a, int b): a / b の結果を返す。ただし b が 0 のときは IllegalArgumentException を投げる。メッセージは 0 では割れません とする

9-A で見たように、b が 0 のままだと ArithmeticException で異常終了します。これを、divide の中であらかじめ 0 を弾き、不正な引数だと分かるメッセージの例外を投げる形にします。

まず Main から 0 を渡して実行してください。divide の中で例外が投げられ、異常終了するはずです。

Main.java
Calculator calc = new Calculator();
System.out.println(calc.divide(100, 0));
Exception in thread "main" java.lang.IllegalArgumentException: 0 では割れません
at Calculator.divide(Calculator.java:3)
at Main.main(Main.java:2)

0 を渡したのは Main 側の誤りです。これはコードを直せば防げるエラーなので、try-catch で捕まえるのではなく、calc.divide(100, 5) のように 0 以外を渡すよう Main を修正してください。

出力例:

20
ヒント

divide の先頭で if (b == 0) を判定し、throw new IllegalArgumentException("0 では割れません"); を実行します。0 を弾いたあとで return a / b; を書けば、b が 0 のときに割り算へ進むことはありません。IllegalArgumentException は非チェック例外なので、Main は捕まえずに、正しい引数を渡すよう直します。

銀行口座を表す BankAccount クラスを作ってください。次のとおり定義します。

  • フィールド: balance(int、残高)
  • コンストラクターで初期残高を受け取って初期化する
  • withdraw(int amount): 残高から amount を引く。残高が足りないときは IllegalStateException を投げる。メッセージは 残高不足です。残高: ◯◯円、要求: ◯◯円 の形式にする
  • getBalance(): 現在の残高を返す

withdraw は、amount の値ではなく、そのときの残高との関係で成否が決まります。残高 3000 円のとき 2000 円は引き出せますが、5000 円は引き出せません。引き出せない状態を IllegalStateException で表します。

Main.java
BankAccount account = new BankAccount(3000);
account.withdraw(2000);
System.out.println("残高: " + account.getBalance() + "");
account.withdraw(5000); // 残高 1000 円では引き出せない
残高: 1000円
Exception in thread "main" java.lang.IllegalStateException: 残高不足です。残高: 1000円、要求: 5000円
at BankAccount.withdraw(BankAccount.java:8)
at Main.main(Main.java:6)

最初の引き出しは成功し、2 回目で残高不足の例外が投げられて異常終了することを確認してください。

ヒント

withdraw の先頭で if (amount > balance) を判定し、throw new IllegalStateException("残高不足です。残高: " + balance + "円、要求: " + amount + "円"); を実行します。判定を通過したら balance -= amount; で残高を減らします。BankAccount は次の問題でも使います。

9-D. throws でチェック例外を伝える

Section titled “9-D. throws でチェック例外を伝える”

ファイルの全行を読み込む Files.readAllLines は、ファイルが存在しないなどの理由でチェック例外 IOException を発生させます。設定ファイルを読み込んで行数を返す ConfigLoader クラスを作ってください。

  • countLines(String fileName): Files.readAllLines(Path.of(fileName)) で全行を読み込み、行数(int)を返す。IOException は自分で処理せず、throws で呼び出し元に伝える

ファイルが存在するかどうかは、プログラムの書き方では防げません。そのため IOException はチェック例外で、countLines を呼ぶ側に対処を求めます。Main.java では try-catch で捕まえ、存在しないファイル名を渡して catch に入ることを確認してください。

Main.java
import java.io.IOException;
ConfigLoader loader = new ConfigLoader();
try {
int lines = loader.countLines("config.txt");
System.out.println("行数: " + lines);
} catch (IOException e) {
System.out.println("設定ファイルを読み込めません: " + e.getMessage());
}
ヒント

ConfigLoader の先頭に import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path; を書きます。countLines メソッドの宣言は public int countLines(String fileName) throws IOException で、Files.readAllLines(Path.of(fileName)).size() を返します。

IOException はチェック例外なので、countLines を呼ぶ Main 側では try-catch が必須です。

9-E. チェック例外のコンパイルエラー

Section titled “9-E. チェック例外のコンパイルエラー”

次の Main.java はコンパイルエラーになります。エラーの理由を説明し、コンパイルが通るように修正してください。9-D の ConfigLoader をそのまま使います。

Main.java
ConfigLoader loader = new ConfigLoader();
int lines = loader.countLines("config.txt");
System.out.println("行数: " + lines);
ヒント

countLines はチェック例外 IOExceptionthrows で宣言しています。チェック例外は、try-catch で捕まえるか throws で呼び出し元に伝えるかのどちらかが必須で、どちらもないとコンパイルエラーになります。

演習 9-D のように呼び出しを try-catch で囲むと、コンパイルが通ります。

finally ブロックは、例外が発生してもしなくても必ず実行されます。9-B の Calculator を使い、割り算の結果を表示する処理を try-catch-finally で書いてください。

  • trycalc.divide(a, b) の結果を出力する
  • catchIllegalArgumentException を捕まえ、計算できません: のあとにメッセージを出力する
  • finally計算を終了します と出力する

b に 0 を渡した場合と、0 以外を渡した場合の両方を実行し、どちらでも finally が実行されることを確認してください。

Main.java
Calculator calc = new Calculator();
try {
System.out.println("結果: " + calc.divide(100, 0));
} catch (IllegalArgumentException e) {
System.out.println("計算できません: " + e.getMessage());
} finally {
System.out.println("計算を終了します");
}

出力例:

計算できません: 0 では割れません
計算を終了します
ヒント

finally ブロックは catch の後ろに書きます。例外が発生したときは catchfinally の順、発生しないときは try の最後 → finally の順で実行されます。divide(100, 5) に変えると、結果: 20計算を終了します の両方が出力されます。

9-G. try-with-resources でリソースを閉じる

Section titled “9-G. try-with-resources でリソースを閉じる”

Scanner でキーボードから入力を読み取ります。Scanner は使い終わったら close で閉じる必要があるリソースです。まず、close を手で書いたコードを main メソッドに書きます。

Main.java
import java.util.Scanner;
Scanner scanner = new Scanner(System.in);
System.out.print("名前を入力: ");
String name = scanner.nextLine();
System.out.println("こんにちは、" + name + "さん");
scanner.close();

この書き方では、途中で例外が発生すると scanner.close() の行に到達せず、閉じられないまま終わります。これを、ブロックを抜けるときに自動で閉じる try-with-resources に書き直してください。書き直すと、例外が発生しても確実に閉じられ、scanner.close() を自分で書く必要もなくなります。

ヒント

try (Scanner scanner = new Scanner(System.in)) { ... } のように、try の括弧の中で scanner を宣言します。ブロックを正常に抜けても例外で抜けても、scanner が自動で閉じられます。close を手で書く行は削除します。

9-H. 例外の握りつぶしを観察する

Section titled “9-H. 例外の握りつぶしを観察する”

9-C の BankAccount を使い、引き出し処理を呼び出します。次の Main.java は、withdraw が投げる IllegalStateExceptioncatch で捕まえていますが、catch が空のため失敗に気付けません。

Main.java
BankAccount account = new BankAccount(3000);
try {
account.withdraw(5000);
} catch (IllegalStateException e) {
// 何もしない
}
System.out.println("引き出し完了。残高: " + account.getBalance() + "");

残高 3000 円から 5000 円は引き出せないため withdraw は例外を投げますが、空の catch がそれを握りつぶし、引き出し完了。残高: 3000円 と表示されて成功したように見えます。catch でエラーを記録し、失敗したあとは引き出し完了の出力へ進まないように直してください。catchSystem.err.println("引き出しに失敗しました: " + e.getMessage()); でエラーを記録し、続けて return; で処理を打ち切ります。

直したあとは、エラーの記録だけが出力され、引き出し完了 は出力されません。

出力例:

引き出しに失敗しました: 残高不足です。残高: 3000円、要求: 5000円
ヒント

空の catch は、例外が起きたことをなかったことにしてしまいます。エラーを記録するだけでなく、失敗したあとの処理を続けないことが握りつぶしとの違いです。System.err でエラーを記録し、return; で引き出し完了の出力へ進まないようにします。