メインコンテンツへスキップ
見出し画像

3分間プログラミング講座 第12回:プログラムの「困った!」を乗り越える~例外処理(Exception Handling)~


    登場人物:

    • チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。

    • ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。


    ボット君: 先生!前回のstatic修飾子、すごく便利でした!オブジェクトを作らずに共通の機能を使えるって、まさに「みんなの道具」って感じですね!

    先生: その調子だ、ボット君!さて、今日のテーマは、プログラムを作っていると必ず直面する「困った!」をどう乗り越えるか、つまり「例外処理(Exception Handling)」だ。

    ボット君: 困った!ですか?プログラムって、いつも完璧に動くわけじゃないんですか?

    先生: 残念ながら、プログラムは常に完璧に動くとは限らないんだ。例えば、ファイルを開こうとしたらファイルがなかったり、ユーザーが数値を入れるべき場所に変な文字列を入力してしまったり、インターネットに接続しようとしたら回線が切れていたり…色々な「予期せぬ事態」が起こりうる。

    ボット君: ああ、確かに!そういう時、プログラムが急に止まっちゃうと困りますね…。

    先生: そうだね。そんな時にプログラムが「異常終了」してしまうのを防ぎ、適切に対処して、できるだけ正常な動作を続けさせるための仕組みが「例外処理」なんだ。



    例外とは?~プログラムの「異常事態」~



    先生: まず「例外(Exception)」とは何かを理解しよう。例外とは、プログラムの実行中に発生する「予期せぬイベント」や「異常な状態」のことだ。これはプログラムのバグとは少し違う。バグはプログラマのミスだけど、例外は「外部の状況」や「ユーザーの操作」など、実行時にならないと分からない要因で起こるエラーのようなものだ。

    ボット君: なるほど。エラーだけど、プログラムが悪いわけじゃない場合もあるんですね。

    先生: その通り。Javaでは、この例外をExceptionというクラスのオブジェクトとして表現するんだ。そして、例外が発生することを「例外をスローする(throw)」と表現する。



    例外処理の基本構造: try-catch文



    先生: 例外が発生する可能性がある処理は、「tryブロック」という特別なブロックの中に書く。そして、例外が発生した場合にどう対処するかは、「catchブロック」に記述するんだ。

    Java

    try {
        // 例外が発生する可能性がある処理
    } catch (例外の型 変数名) {
        // 例外が発生した場合の処理(catchブロックが実行される)
    }
    

    ボット君: ふむふむ、試してみて、もし捕まえたら何かする、って感じですね!

    先生: その通り!実際に例を見てみよう。

    Java

    public class TryCatchExample {
        public static void main(String[] args) {
            // 例1: 0での割り算 (ArithmeticException)
            try {
                int result = 10 / 0; // ここでArithmeticExceptionが発生する
                System.out.println("計算結果: " + result); // この行は実行されない
            } catch (ArithmeticException e) {
                // ArithmeticExceptionが発生した場合の処理
                System.err.println("エラー: 0で割ることはできません。");
                System.err.println("例外メッセージ: " + e.getMessage()); // 例外の詳細メッセージ
                // e.printStackTrace(); // 例外が発生した場所(スタックトレース)を表示することもできる
            }
    
            System.out.println("\nプログラムは続行されます。"); // 例外処理後もプログラムは終了しない
        }
    }
    

    先生: この例では、10 / 0という0での割り算は、数学的に定義できないためArithmeticExceptionという例外が発生する。tryブロック内でこの例外がスローされると、それ以降のtryブロック内の処理は実行されず、対応するcatch (ArithmeticException e)ブロックの処理が実行されるんだ。そして、catchブロックの処理が終われば、プログラムは異常終了せずに続行される。

    ボット君: なるほど!もしtry-catchがなかったら、プログラムが途中で止まっちゃうってことですね!

    先生: その通り!例えば、ユーザーからの入力を受け取って数値を計算するプログラムで、ユーザーが間違って文字を入力したら、NumberFormatExceptionという例外が発生する。そんなときも、try-catchで適切に処理すれば、ユーザーに「数値を入力してください」とメッセージを出して再入力を促すなど、親切なプログラムにできるんだ。



    複数の例外をキャッチする



    先生: 一つのtryブロックで複数の種類の例外が発生する可能性がある場合、catchブロックを複数記述することもできる。

    Java

    import java.util.InputMismatchException;
    import java.util.Scanner;
    
    public class MultiCatchExample {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
    
            try {
                System.out.print("最初の数値を入力してください: ");
                int num1 = scanner.nextInt(); // InputMismatchExceptionが発生する可能性
    
                System.out.print("2番目の数値を入力してください: ");
                int num2 = scanner.nextInt(); // InputMismatchExceptionが発生する可能性
    
                int result = num1 / num2; // ArithmeticExceptionが発生する可能性
                System.out.println("結果: " + result);
    
            } catch (InputMismatchException e) {
                System.err.println("エラー: 数値ではない入力がありました。");
                scanner.next(); // 不正な入力を読み飛ばす
            } catch (ArithmeticException e) {
                System.err.println("エラー: 0で割ることはできません。");
            } catch (Exception e) { // その他の全ての例外をキャッチ (汎用的な例外ハンドリング)
                System.err.println("予期せぬエラーが発生しました: " + e.getMessage());
            } finally {
                // 例外の有無にかかわらず、必ず実行されるブロック
                System.out.println("処理を終了します。スキャナーを閉じます。");
                scanner.close();
            }
    
            System.out.println("プログラムは正常に終了しました。");
        }
    }
    

    先生: この例では、InputMismatchException(入力が数値ではない場合)とArithmeticException(0で割り算した場合)という二つの異なる例外をキャッチしている。catchブロックは上から順に評価されるので、より具体的な例外を先に、より汎用的な例外(Exceptionなど)を後に書くのがセオリーだ。



    finallyブロック



    先生: さらに、「finallyブロック」というものもある。これは、tryブロック内の処理が例外を発生させてもさせなくても、必ず実行されるブロックだ。

    ボット君: 必ず実行される?どんな時に使うんですか?

    先生: データベース接続のクローズやファイルのクローズなど、プログラムがどんな状況になっても、**必ず解放しなければならないリソース(資源)**がある場合に使うんだ。たとえ例外が発生して処理が中断されても、finallyブロックは実行されるので、リソースの解放忘れを防ぐことができる。さっきの例でもscanner.close()をfinallyブロックに入れているね。これは、入力エラーや計算エラーが発生しても、スキャナーリソースを確実に解放するためだ。



    Checked ExceptionとUnchecked Exception



    先生: Javaの例外には大きく分けて二つの種類がある。

    1. Checked Exception(検査例外):

      • コンパイル時に、例外処理を記述することが義務付けられる例外。

      • ファイルが見つからない(FileNotFoundException)など、外部要因による予期せぬ事態が多い。

      • 呼び出し元にtry-catchで処理させるか、throwsキーワードで「このメソッドは例外をスローする可能性があります」と宣言する必要がある。

    2. Unchecked Exception(非検査例外):

      • RuntimeExceptionとそのサブクラス、およびErrorとそのサブクラス。

      • コンパイル時に例外処理を記述することが義務付けられない例外。

      • プログラマのミス(バグ)に起因することが多く、通常はプログラムの修正で対処する。例えば、0での割り算(ArithmeticException)や、配列の範囲外アクセス(ArrayIndexOutOfBoundsException)など。

    ボット君: えー!コンパイル時にチェックされる例外とされない例外があるんですか!

    先生: そうなんだ。Checked Exceptionは、プログラマがその例外の発生を意識し、適切に対処するよう強制することで、より堅牢なプログラムを作るための仕組みだ。一方、Unchecked Exceptionは、ほとんどがバグなので、コードを修正すべきケースが多いという考え方だね。



    例外の階層構造



    先生: 参考までに、Javaの例外階層を簡単に説明しておこう。

    Throwable
    ├── Error (システムレベルのエラー、通常は捕捉しない)
    └── Exception
        ├── RuntimeException (Unchecked Exception)
        │   ├── NullPointerException
        │   ├── ArithmeticException
        │   └── ArrayIndexOutOfBoundsException等
        └── その他のException (Checked Exception)
            ├── IOException
            ├── SQLException
            └── ClassNotFoundException等
    


    例外処理のメリット



    先生: 例外処理を適切に行うことで、どんなメリットがあると思う?

    ボット君: プログラムが急に止まらなくなる!あとは、エラーが起きてもユーザーに分かりやすいメッセージが出せる、とか!

    先生: その通り!主なメリットは以下の通りだ。

    • 堅牢性の向上: 予期せぬ事態が発生してもプログラムが異常終了せず、安定して動作し続ける。

    • エラー原因の特定: 例外オブジェクトからエラーの種類や発生場所などの詳細情報を取得できるため、デバッグがしやすくなる。

    • コードの分離: 通常の処理ロジックとエラー処理ロジックを分離できるため、コードが読みやすくなる。

    • ユーザー体験の向上: 異常な状況でもユーザーに適切なメッセージを表示したり、代替処理を提供したりできる。



    今日のまとめ!



    • 例外(Exception): プログラム実行中に発生する「予期せぬイベント」や「異常な状態」。

    • try-catch文: 例外が発生する可能性のある処理をtryブロックに、例外発生時の対処をcatchブロックに記述する。

    • finallyブロック: 例外の有無にかかわらず、必ず実行される処理(リソースの解放など)を記述する。

    • Checked Exception: コンパイル時に処理が義務付けられる例外(外部要因など)。try-catchまたはthrowsが必要。

    • Unchecked Exception: RuntimeExceptionとそのサブクラス、およびErrorのサブクラス。コンパイル時に処理が義務付けられない例外で、プログラマのミス(バグ)に起因することが多い。

    • 例外の階層: Throwableの下にErrorとExceptionがあり、Exceptionの下にRuntimeException(Unchecked)とその他の例外(Checked)がある。

    • メリット: プログラムの堅牢性向上、エラー原因の特定、コードの可読性向上、ユーザー体験向上。


    ボット君: 先生、今日は「困った!」を乗り越える方法を学べて、すごく安心しました!これで、もっと安定したプログラムが書けそうです!

    先生: その調子だ、ボット君!例外処理は、信頼性の高いシステムを作る上で欠かせない技術だよ。次回は、より高度な設計の考え方、「抽象化」と「具象化」、そして**DI(依存性注入)**の概念の入り口を覗いてみよう!

    ボット君: 抽象化に具象化、DI!なんだかすごく難しそうですけど、楽しみです!ありがとうございました!


    次回:第13回:抽象化・具体化・DI ~Spring Frameworkにおける設計の柔軟性~

    #オブジェクト指向 #プログラミング初心者 #Java #クラスとメソッド #プログラミング学習 #入門


     
     
     
    まだ知らない『面白い』への案内人。人・作品・AIの魅力を見つけ、物語と知識に変えて届けるコンシェルジュです。毎週土曜は漫画ソムリエ。スキ動画コンテストほか、いろいろ実験中。

    あなたへのおすすめ