6日でできる 新Java入門|例外処理

エラーに負けない力を身につけよう。例外処理は、Javaプログラムを守るための防御技だ。

Javaでプログラムを書いていると、思い通りに処理が進む場面ばかりではありません。数字を入れてほしいところに文字が入力されたり、必要なファイルが見つからなかったり、まだ何も入っていないデータを使おうとして止まってしまったりすることがあります。

こうした場面は、バトル中に突然足元が崩れたり、想定外の攻撃が飛んできたりするようなものです。どれだけしっかり修行していても、予想外の事態そのものを完全になくすことはできません。大切なのは、異常事態が起きたときにどう受け止め、どう立て直すかです。

Javaの例外処理は、まさにそのための仕組みです。危険が起こりそうな場所を見張り、もし問題が発生したら、その場で適切に受け止めて安全な流れへ戻します。これによって、プログラムはただ止まるだけではなく、利用者にわかりやすく状況を伝えたり、後片付けをしてから終了したりできます。

例外処理を学ぶと、プログラムは一気に実戦向きになります。強いプログラムとは、何も起こらないときだけ動くものではありません。トラブルが起きても冷静に対処できるものです。ここでは、そんな例外処理の基本を、ドラゴンボールの修行の世界観にたとえながら、やさしく丁寧に見ていきましょう。

例外とは何か

例外とは、プログラムの通常の流れでは処理しきれない特別な出来事のことです。ふだんの流れで順調に進んでいた処理が、ある瞬間に「このままでは危ない」と判断され、別の対応ルートへ切り替わるイメージです。

ドラゴンボール風にたとえるなら、修行場でいつものメニューをこなしていたところに、突然予想外のハプニングが起きるようなものです。たとえば、

  • 修行回数を数字で記録するはずなのに、文字が書かれていた
  • 技の一覧の5番目を見ようとしたのに、実際には3つしかなかった
  • 道具箱を使おうとしたら、中身が何も入っていなかった
  • 修行記録の巻物を読もうとしたのに、巻物自体が見つからなかった

こうした出来事を、Javaでは例外として扱います。

例外の種類を見てみよう

Javaでは、例外は Exception クラスを中心とした仕組みで管理されています。ドラゴンボール風に考えるなら、Exception は「異常事態の戦士たちをまとめる大きな一族」のような存在です。その中に、それぞれ特徴の違う戦士がいます。

例外クラスどんなときに起こるかドラゴンボール風のイメージ
NumberFormatException数字に変換できない文字列を数値として扱ったとき戦闘力を数値で読むはずなのに、謎の文字が混ざっていた
ArrayIndexOutOfBoundsException配列の範囲外を使おうとしたとき3人しかいない修行仲間の4人目を呼ぼうとした
IOException入出力で問題が起きたとき修行記録の巻物を読もうとしたが開けなかった
NullPointerException何もない参照に対してメソッドを呼び出したときまだ気が宿っていない装備に向かって技を出そうとした

ここで大事なのは、例外ごとに原因が違うということです。原因が違うなら、対処のしかたも変わります。だからJavaでは、どの種類の例外が起きたのかを見分けて、それぞれに合った対応を行えるようになっています。

例外が起きると何が起こるのか

例外が発生すると、プログラムはその場で「普通の流れ」を続けることができなくなります。何も対処していなければ、実行が止まり、エラーメッセージが表示されて終了してしまいます。

たとえば、文字列を int に変換する Integer.parseInt() で、数字ではない文字を渡すと NumberFormatException が発生します。

これは、ドラゴンボールでいえば、修行の途中で突然強い衝撃を受け、そのまま前進できなくなる状態に近いです。無理に進めるのではなく、一度状況を受け止めて立て直す必要があります。

その立て直しを行うのが、例外処理です。

例外処理の基本構文

Javaで例外処理を行うときの中心になるのが、try と catch です。

try の役割

try は、例外が起こるかもしれない処理を囲むブロックです。
ここは、いわば「危険が発生するかもしれない修行エリア」です。

try {
    // 例外が発生するかもしれない処理
} catch (例外クラス 変数名) {
    // 例外が発生したときの処理
}

catch の役割

catch は、発生した例外を受け止める場所です。
例外が起きた瞬間、プログラムは try の中の処理を続けるのではなく、対応する catch へ移動します。

これは、敵の奇襲を受けた瞬間に、防御担当の戦士がすぐ前に出てくるようなものです。

Lesson26_1.java で例外処理を学ぼう

ここでは、ユーザーに修行回数を入力してもらい、整数が正しく入力されたときだけ受付を完了するプログラムを見ていきます。数字ではないものが入力された場合は、やさしく再入力をうながします。

ファイル名:Lesson26_1.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Lesson26_1 {
    public static void main(String[] args) throws IOException {
        // キーボード入力を受け取るための準備
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        int trainingCount;

        while (true) {
            System.out.print("今日の修行回数を入力してください > ");

            String input = reader.readLine();

            try {
                // 入力文字列を整数に変換する
                trainingCount = Integer.parseInt(input);

                System.out.println("修行回数 " + trainingCount + " 回で登録しました。");
                System.out.println("今日も一歩ずつ力を高めていきましょう。");
                break;
            } catch (NumberFormatException e) {
                System.out.println("数字として読み取れませんでした。");
                System.out.println("半角の整数で入力し直してください。");
            }
        }
    }
}

実行例

今日の修行回数を入力してください > abc
数字として読み取れませんでした。
半角の整数で入力し直してください。
今日の修行回数を入力してください > 30
修行回数 30 回で登録しました。
今日も一歩ずつ力を高めていきましょう。

このプログラムで注目したい流れ

このプログラムでは、入力された文字列を Integer.parseInt(input) で整数に変換しています。
ここが例外の発生ポイントです。

処理の流れを整理すると、次のようになります。

流れ内容
1ユーザーに修行回数を入力してもらう
2入力値を文字列として受け取る
3try の中で整数へ変換する
4正しく変換できたらメッセージを表示して終了する
5変換できなければ NumberFormatException が発生する
6catch に移動し、再入力をうながす
7while 文でもう一度入力を受け付ける

つまり、try は「技を放つ場面」、catch は「うまくいかなかったときの受け身」と考えるとわかりやすいです。

try と catch の動きをもう少し丁寧に理解しよう

try の中で問題が起きなかった場合

try の中の処理がすべて正常に終われば、catch は実行されません。
そのまま次の処理へ進みます。

今回のプログラムでは、30 のような整数が入力された場合、parseInt() が成功するので catch は動きません。

try の中で問題が起きた場合

たとえば abc を入力すると、整数への変換に失敗します。すると NumberFormatException が発生し、その時点で try の残りの処理は中断されます。そして catch に処理が移ります。

この切り替えこそが例外処理の重要なポイントです。
異常が起きたときに、プログラムを無理に前へ進めず、安全な対応ルートへ切り替えているのです。

catch の引数にある e とは何か

catch の後ろには、次のような書き方がありました。

catch (NumberFormatException e) {

この e は、発生した例外の情報を受け取るための変数です。
いわば「異常事態の報告書」を受け取る役目です。

この e を使うと、何が起きたのか詳しい情報を取り出せます。たとえば getMessage() を使えば、例外の詳細メッセージを取り出せます。

catch (NumberFormatException e) {
    System.out.println("エラーの詳細: " + e.getMessage());
}

このように書くと、単にエラーでしたと伝えるだけでなく、原因に近い情報を表示できます。開発中の確認にも役立ちますし、ログ出力にもつながる大事な考え方です。

複数の例外に対応する考え方

現実のプログラムでは、1種類の例外だけを気にすればよいとは限りません。
たとえば、

  • 数値変換に失敗するかもしれない
  • 入力そのものの読み取りで問題が起きるかもしれない

というように、別々の種類の異常が起こることがあります。

そのようなときは、catch を複数並べて書くことができます。

try {
    // 例外が起きるかもしれない処理
} catch (NumberFormatException e) {
    System.out.println("数値の変換で問題が起きました: " + e.getMessage());
} catch (IOException e) {
    System.out.println("入力の読み取りで問題が起きました: " + e.getMessage());
}

これは、敵の攻撃の種類ごとに受け方を変えるようなものです。
気弾には気弾の受け方、打撃には打撃の受け方がある、という感覚に近いです。

どの catch が実行されるのか

発生した例外の種類に合う catch が実行されます。
NumberFormatException ならその catch、IOException ならその catch が使われます。

もし合う catch がなければ、その例外はそこで処理されず、さらに外側へ伝わっていきます。

すべての例外をまとめて受け止める Exception

すべての例外をひとまとめで受け止めたいときは、Exception を使うこともできます。

catch (Exception e) {
    System.out.println("何らかの問題が発生しました: " + e.getMessage());
}

これはとても便利に見えますが、使い方には少し注意が必要です。

Exception でまとめる利点

  • 一括で異常を受け止められる
  • とりあえず落ちないようにする緊急対応がしやすい

Exception でまとめすぎる注意点

  • どんな問題が起きたのか見えにくくなる
  • 本来は分けて対処すべき例外まで同じ扱いにしてしまう

ドラゴンボール風に言えば、どんな敵にも同じ構えで対応するようなものです。
それでも防げる場面はありますが、相手の特徴に合わせて戦い方を変えたほうが、やはり安定します。

そのため、実務ではできるだけ具体的な例外クラスごとに対処する意識が大切です。

throw で自分から例外を発生させる

例外は、Javaが勝手に出すものだけではありません。
プログラムを書く側が、自分で必要だと判断して発生させることもできます。これが throw です。

if (trainingCount < 0) {
    throw new IllegalArgumentException("修行回数にマイナスは指定できません: " + trainingCount);
}

これは、明らかにおかしい値に対して「このまま先へ進ませない」という意思表示です。

ドラゴンボール風にたとえるなら、修行場の入口で「この条件では入場不可」と判断して止める門番のような役目です。

throw を使う場面

  • 不正な引数が渡されたとき
  • 本来ありえない状態になったとき
  • その先の処理を続けると危険なとき

例外処理は、起きた問題に後から対応するだけではありません。
危険な状態を見つけた時点で、自分から異常を宣言して安全を守ることもできます。

throws は外へ伝える宣言

メソッドの中で起きた例外を、そのメソッドの中では処理せず、呼び出し元へ渡すときに使うのが throws です。

public static void main(String[] args) throws IOException {

Lesson26_1.java でも、このように main() に throws IOException をつけています。これは、readLine() のような入出力処理で問題が起きた場合、その例外を main() の外側へ伝えるという意味です。

throw と throws の違い

この2つは名前が似ていますが、役割は違います。

用語役割イメージ
throw実際に例外を発生させるその場で危険信号を出す
throwsこのメソッドは例外を外へ渡すと宣言する危険が来たら次の担当者へ引き継ぐと伝える

ここは混同しやすいので、しっかり分けて覚えておくと安心です。

finally は最後に必ず動く

例外処理では finally もよく使われます。
finally は、例外が起きても起きなくても、最後に必ず実行されるブロックです。

try {
    // 処理
} catch (Exception e) {
    // 例外処理
} finally {
    // 必ず実行したい後片付け
}

finally が活躍する場面

  • ファイルを閉じる
  • ネットワーク接続を終了する
  • 一時的に使った資源を解放する

ドラゴンボール風にいえば、戦闘が終わったあとに必ず行う片付けや撤収確認です。勝っても負けても、最後の整理は必要です。

最近のJavaでは try-with-resources という便利な書き方もありますが、finally の考え方そのものはとても大切です。後始末を忘れないための基本として押さえておきましょう。

例外処理を覚えるとプログラムはどう変わるか

例外処理を使わないプログラムは、想定外の事態にとても弱いです。少しでも入力がずれたり、環境に問題が起きたりすると、そのまま止まってしまうことがあります。

一方で例外処理を使うと、次のような改善ができます。

改善される点内容
安全性異常時に無理な処理を続けない
わかりやすさ利用者に意味のあるメッセージを表示できる
保守性どこで何が起きたか整理しやすい
信頼性異常が起きても落ち着いて対応できる

プログラムは、成功する場面だけを考えて作ると、実際の運用で弱くなります。
例外処理を学ぶことは、失敗を前提にした強い設計を学ぶことでもあります。

ドラゴンボール風に整理する例外処理のイメージ

ここで、例外処理全体の役割をドラゴンボール風にまとめてみましょう。

Javaの仕組みドラゴンボール風のたとえ
try危険が潜む修行エリアに入る
catch想定外の攻撃を受け止めて立て直す
finally戦闘後に必ず行う後片付け
throw自分で危険信号を出して中断する
throws上位の戦士や指揮官へ対応を引き継ぐ
Exception異常事態全体を表す大きな分類
NumberFormatException数字のはずが数字でなかった異常
IOException入出力まわりで起きた異常

このように見ていくと、例外処理は単なる構文暗記ではなく、「異常が起きたときにどう動くか」という設計の考え方だとわかります。

図:例外処理の流れ

↓クリックすると拡大表示されます。

この図が示していること

この図は、入力値を整数へ変換する処理の中で、成功した場合と失敗した場合に流れがどう分かれるのかを表したものです。
try の中で処理を実行し、問題がなければ成功ルートへ進み、例外が起きたら catch に移動する、という例外処理の基本動作をひと目で確認できます。

この図から分かること

この図を見ると、例外処理は「エラーが起きたらただ止まる仕組み」ではなく、「異常時に別ルートへ切り替えて安全に対応する仕組み」であることが分ります。

例外処理を学ぶときに意識したいこと

例外処理を勉強し始めたときは、どうしても構文だけに目が向きがちです。けれど本当に大切なのは、どこで異常が起きそうか、起きたらどう対応したいかを考えることです。

特に意識したいのは次の3点です。

どこで例外が起こりそうかを見つける

  • 数値変換
  • 配列アクセス
  • ファイル操作
  • キーボード入力
  • ネットワーク通信

こうした部分は、例外が起こりやすい代表例です。

利用者にどう伝えるかを考える

エラーが起きたとき、開発者には意味がわかるメッセージでも、利用者には難しすぎることがあります。
そのため、catch の中では利用者にとってわかりやすい日本語メッセージを出す工夫が大切です。

必要に応じて処理を続けるか、終了するかを決める

異常が起きても再入力で回復できるなら続行できます。
一方で、続行すると危険なケースなら、そこで安全に終了したほうがよいです。

この判断ができるようになると、プログラムの設計力がぐっと高まります。

おわりに代えて

例外処理は、Javaの中でもとても実践的なテーマです。
うまく書けるようになると、プログラムは急に「動くだけのもの」から「安心して使えるもの」へ変わっていきます。

最初は try、catch、throw、throws、finally と覚えることが少し多く感じるかもしれません。でも、ひとつずつ役割を整理していけば大丈夫です。Lesson26_1.java のような小さな例から試していくと、例外処理の流れがしっかり身についていきます。

異常が起きても落ち着いて受け止める。
それは、プログラムにとっての大切な防御技です。
例外処理を身につけて、トラブルにも強いJavaプログラムを書けるようになっていきましょう。