Javaでの例外処理の完全ガイド!初心者から上級者までの7つの手法

Javaプログラムの画面に表示されるエラーメッセージとコードの断片Java
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaのプログラミングの世界では、エラーという未予期の事態が発生することは避けられません。

これらのエラーを効果的にハンドリングする技術が、例外処理です。

例外処理はプログラムの安定性と信頼性を高める重要な要素であり、Javaでのプログラム作成においても基礎となる知識といえます。

この記事では、Javaの例外処理の基本から高度なテクニックまでを、段階的かつ詳細に説明します。

また、それぞれの段階でサンプルコードを表し、そのコードがどのような動作をするのか、そしてどのような結果をもたらすのかを明示します。

さらに、そのコードの背後にある理論やコンセプトも分かりやすく説明しますので、初心者でも安心して学習を進めることができます。

Javaの例外処理を学ぶことで、あなたのプログラムはより堅牢かつ信頼性が高くなります。

●Javaの例外処理とは

Javaの例外処理は、プログラム中で発生する可能性のあるエラーを効果的にハンドリングする方法の一つです。

これによってプログラムが予期せぬエラーでクラッシュするのを防ぐことができます。

例外処理はJava言語の核心的な部分であり、効率的かつ安全なプログラムを開発するためには欠かせない技術といえます。

○例外処理の基本概念

例外とは、プログラムが正常に動作することを妨げる可能性のあるエラーや異常状態のことを言います。

Javaにおける例外処理は、これらの例外が発生したときに、適切な方法で対処する仕組みを提供します。

例外は基本的には二種類に分けられます:検査例外と非検査例外。

検査例外はコンパイル時に検知され、適切な例外処理をプログラマが提供することが強制されます。

一方で、非検査例外は実行時に発生し、事前に対処することが推奨されますが、強制ではありません。

○Javaにおける例外の種類

Javaにおける例外は主に三種類に分類されます。

検査例外、実行時例外、そしてエラーです。

それぞれの特性と代表的な例を説明します。

□検査例外(Checked Exceptions)

これらはコンパイラによって検査される例外であり、プログラマはこれらをキャッチするか、再スローする必要があります。

例としては、IOExceptionやSQLExceptionがあります。

□実行時例外(Runtime Exceptions)

これらはコンパイラによって検査されない例外です。

プログラムのバグや論理エラーが原因となる場合が多く、RuntimeExceptionクラスを継承する例外がこれに該当します。

NullPointerExceptionやArrayIndexOutOfBoundsExceptionが代表的な例です。

□エラー(Errors)

これはシステムレベルの重大な問題を示すもので、通常、アプリケーションコードでキャッチすることは推奨されません。

OutOfMemoryErrorやStackOverflowErrorがこれに該当します。

●例外処理の基本手法

Javaのプログラムを書いている際、必ずと言っていいほど遭遇するのが例外です。

例外とは、プログラムの実行中に予期しない事態やエラーが発生した際にそれをキャッチし、適切に処理を行うための仕組みです。

Javaにはこれを効率的に行うためのいくつかの基本的な手法や文法が用意されています。

○try-catch文の使用方法

Javaにおいて例外処理を行う最も基本的な手法は、try-catch文を使用することです。

この文を使用することで、エラーが発生する可能性があるコードをtryブロック内に記述し、もしエラーが発生した場合はcatchブロック内でその例外をキャッチして処理を行います。

□サンプルコード1:基本的なtry-catch文の使用例

整数の除算を行う際に0での除算が行われる可能性を考慮して、その例外をキャッチする基本的なサンプルコードを紹介します。

public class ExceptionSample {
    public static void main(String[] args) {
        int number = 10;
        int divisor = 0;
        try {
            int result = number / divisor;
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("0での除算はできません。");
        }
    }
}

このコードではnumber / divisorという除算を試みています。

しかし、divisorが0の場合、ArithmeticExceptionが発生します。

この例外をキャッチするために、除算のコードはtryブロック内に記述されており、例外が発生した際の処理はcatchブロック内で行われています。

このコードを実行すると、”0での除算はできません。”というメッセージがコンソールに表示されます。

これは、0での除算が行われたためArithmeticExceptionが発生し、その例外がキャッチされた結果です。

○try-catch-finally文の詳細

Javaでのプログラミング作業において、例外処理は避けて通れない大切なスキルの一つです。

その中でも、try-catch-finally文はJavaの例外処理の中核を担っています。

try-catch-finally文は、Javaの例外をキャッチし、適切に処理するための構文です。

具体的には、tryブロックの中で何らかの例外が発生した場合、catchブロックでその例外を捕捉し、適切な処理を行います。

そして、finallyブロックは、例外の発生の有無に関わらず、最後に必ず実行されるブロックです。

このfinallyブロックは、例外が発生してもしなくても、必要な後処理を行うために使われます。

□サンプルコード2:finally節を持つ例外処理

下記のサンプルコードは、try-catch-finally文を使用した例を表しています。

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("0での割り算はできません。");
        } finally {
            System.out.println("このメッセージは必ず表示されます。");
        }
    }
}

このコードでは、tryブロック内で0での除算を試みています。

この操作は、ArithmeticExceptionを引き起こします。

したがって、catchブロックが実行され、エラーメッセージが出力されます。

そして、finallyブロック内のメッセージも出力されます。

このコードを実行すると、次のような出力結果が得られます。

0での割り算はできません。
このメッセージは必ず表示されます。

この出力結果からも、例外が発生してもしなくても、finallyブロックの内容は必ず実行されることがわかります。

したがって、リソースの解放や後処理など、常に実行されるべき処理はfinallyブロック内に記述することが推奨されます。

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

Javaプログラムにおける例外処理は非常に重要な部分であり、適切な方法で例外をキャッチすることが、堅牢で信頼性の高いソフトウェアを構築する上で不可欠です。

ここでは、複数の例外を効率的にキャッチする方法に焦点を当てて詳細に説明いたします。

Javaにはさまざまな種類の例外が存在し、異なる例外が発生する可能性があるコードブロックがあります。

これらの例外を個別にキャッチするのではなく、一度に複数の例外をキャッチする方法を学ぶことで、コードを簡潔かつ読みやすく保つことができます。

では、具体的なコード例とその解説に進みましょう。

□サンプルコード3:複数の例外をキャッチするコード例

Java 7以降では、複数の例外を一つのcatchブロックでキャッチできる「マルチキャッチ」が導入されました。

この方法を利用すると、コードが簡潔になり、異なる例外タイプに対して同じ処理を行うことができます。

下記のサンプルコードは、このマルチキャッチを使用した場合のコード例です。

public class MultipleExceptionHandling {
    public static void main(String[] args) {
        try {
            // 例外を発生させる可能性のあるコード
            if (args.length == 0) {
                throw new IllegalArgumentException("引数が不足しています。");
            } else if (args[0].length() == 0) {
                throw new ArrayIndexOutOfBoundsException("配列のインデックスが範囲外です。");
            }
        } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
            // いずれかの例外が捕捉された場合の処理
            System.out.println("エラー: " + e.getMessage());
        }
    }
}

このコードでは、tryブロック内でIllegalArgumentExceptionやArrayIndexOutOfBoundsExceptionのいずれかの例外が発生する可能性があります。

catchブロックでは、これらの例外を「|」(パイプ)演算子を使用して一つのブロックでキャッチします。

そしてキャッチされた例外のメッセージをコンソールに表示します。

このコードを実行すると、引数がない場合や引数の長さが0の場合に、それぞれ適切な例外メッセージがコンソールに表示されます。

このような方法で、複数の例外を効果的にハンドリングすることができ、コードも読みやすく保つことができます。

●Javaの例外処理の応用技術

Javaの例外処理は基本から応用に至るまで非常に広範なテーマであり、特に応用技術部分では独自の例外クラスの作成方法やそれに関連するコードの実装方法が重要な要素となります。

ここでは、Javaの例外処理の応用技術に焦点を当て、その具体的な使い方と実装方法を深堀りして説明いたします。

○独自の例外クラスの作成方法

Javaプログラミングにおける例外処理の進行段階においては、独自の例外クラスを作成することができます。

これによって、特定の条件や状況で発生する例外を明示的に表現でき、コードの可読性と保守性が向上します。

まず最初に、独自の例外クラスを作成するためには、Exceptionクラスまたはそのサブクラスを拡張することが一般的です。

独自の例外クラスの作成方法を示すサンプルコードとその説明を紹介します。

□サンプルコード4:カスタム例外クラスの作成と使用

Javaにおける独自の例外クラスの作成は非常に簡単であり、次のような形式で実装できます。

public class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

このコードでは、CustomExceptionという名前の新しいクラスを作成しています。

これはExceptionクラスを拡張し、メッセージを受け取るコンストラクタを提供しています。

ここでは、コンストラクタが親クラスのコンストラクタを呼び出し、エラーメッセージを設定します。

次に、このカスタム例外を使用するサンプルコードを見ていきましょう。

public class CustomExceptionTest {
    public static void main(String[] args) {
        try {
            throw new CustomException("独自の例外が発生しました");
        } catch (CustomException e) {
            e.printStackTrace();
        }
    }
}

このコードを実行すると、”独自の例外が発生しました”というメッセージが含まれるCustomExceptionが発生します。

そして、catchブロックに入り、スタックトレースがコンソールに表示されます。

このようにして、独自の例外クラスを作成し利用することができます。

この実装方法は、プロジェクトが複雑化し、特定の種類のエラーを識別しやすくするために非常に有用です。

また、エラーハンドリングのロジックを更に改善し、プログラムの安定性を向上させることができます。

○例外の連鎖とその取り扱い

Javaのプログラミングにおいて、例外が起きることは避けられません。

しかし、例外の連鎖は、一つの例外が別の例外を引き起こす現象として知られています。

これは、コードの中で一つの例外がキャッチされなかった場合、さらに次の処理で新たな例外が発生することを意味します。

この現象は、プログラムの複雑さやエラーハンドリングの欠如に起因することが多いです。

例外の連鎖を適切に処理するためには、次の手法を取り入れることが考えられます。

  1. 原因となる例外を正確にキャッチする
  2. キャッチされた例外を新しい例外として再スローする際、原因となった例外を保持する
  3. キャッチされた例外の情報をログやコンソールに出力して、後のデバッグや分析のための情報を残す

□サンプルコード5:例外の連鎖を利用したコード

public class ChainedExceptionExample {
    public static void main(String[] args) {
        try {
            methodA();
        } catch (HighLevelException e) {
            e.printStackTrace();
        }
    }

    static void methodA() throws HighLevelException {
        try {
            methodB();
        } catch (LowLevelException e) {
            throw new HighLevelException("メソッドAからの例外", e);
        }
    }

    static void methodB() throws LowLevelException {
        throw new LowLevelException("メソッドBの例外");
    }
}

class HighLevelException extends Exception {
    public HighLevelException(String message, Throwable cause) {
        super(message, cause);
    }
}

class LowLevelException extends Exception {
    public LowLevelException(String message) {
        super(message);
    }
}

このコードでは、methodBからLowLevelExceptionがスローされます。

この例外はmethodAでキャッチされ、新たなHighLevelExceptionとして再スローされます。

最終的に、mainメソッドでこのHighLevelExceptionがキャッチされ、スタックトレースが出力されることで、連鎖した例外の情報を正確に取得することができます。

このコードを実行すると、Javaのコンソールには次のように表示されます。

HighLevelExceptionが発生した原因として、LowLevelExceptionの情報も一緒に出力されるのがわかります。

○例外の再スロー技法

Javaプログラムにおける例外処理は非常に重要であり、時には例外を捕捉した後に、条件によっては再度その例外をスローする、いわゆる再スローを行う必要があります。

再スロー技法は、コードの中で例外がキャッチされた後、特定の条件下でその例外をさらに外部へ投げる手法を指します。

ここでは、例外の再スロー技法に焦点を当て、その利用方法と実例を詳細に解説します。

□サンプルコード6:例外の再スローを行うコード例

まずは基本的なコード構造から見ていきましょう。

下記のコードは、一定の条件下で発生する例外を捕捉し、更なる処理を行った後で再度その例外をスローする一例です。

public class ExceptionRethrowExample {
    public static void main(String[] args) {
        try {
            methodThatThrowsException();
        } catch (Exception e) {
            System.out.println("外部で例外をキャッチしました: " + e.getMessage());
            // さらなる処理
        }
    }

    static void methodThatThrowsException() throws Exception {
        try {
            throw new Exception("初期の例外");
        } catch (Exception e) {
            System.out.println("例外をキャッチしました: " + e.getMessage());
            // 何らかの処理を行った後で例外を再スロー
            throw e;
        }
    }
}

このコードの流れを簡単に説明します。

methodThatThrowsExceptionメソッド内で初期の例外がスローされ、それが内部のcatchブロックでキャッチされます。

その際、コンソールにメッセージが出力され、その後、同じ例外が再度スローされます。

そして、再スローされた例外はmainメソッド内のcatchブロックでキャッチされ、再度コンソールにメッセージが出力されます。

実行すると、次のような出力が得られるでしょう。

例外をキャッチしました: 初期の例外
外部で例外をキャッチしました: 初期の例外

このような方法で、例外の再スローを効果的に利用することができます。

この技法は、エラーハンドリングのプロセスを細かく制御する際や、特定の状況でのみ特定の例外をスローするような高度な例外処理ロジックの構築に非常に役立つでしょう。

●例外処理の高度なテクニック

プログラムにおける例外処理は、予期しない問題やエラーが発生したときにそれを捕捉し、適切に処理する技法のことです。

初心者から上級者まで使える高度なテクニックをご紹介いたします。

ここで説明するテクニックは、リソースの自動クローズに関するものです。

Javaの例外処理を理解し適用することで、効率的かつ安全なコードの実装が可能となります。

○リソースの自動クローズとtry-with-resources文

Java7以降、リソースの自動クローズを行う構文としてtry-with-resources文が導入されました。

これは、リソースを開いて使用した後、自動的にクローズされることを保証します。

これにより、リソースのリークを防ぐことができます。

この構文を利用すると、コードがシンプルで可読性が高くなり、かつリソースの管理が効率的に行えるようになります。

□サンプルコード7:try-with-resourcesを使用したリソースの安全なクローズ例

下記のコードは、try-with-resources文を使ったリソースの自動クローズの例です。

このコードは、ファイルの読み込みを行い、最後にファイルを安全にクローズします。

このテクニックを利用すると、finally節でのクローズ処理を記述しなくてもリソースの安全なクローズが保証されます。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        String filePath = "path/to/your/file.txt";

        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("ファイルの読み込みに失敗しました: " + e.getMessage());
        }
    }
}

このコードの解説をいたします。

まず、BufferedReaderクラスのインスタンスを作成し、そのインスタンスをtry-with-resources文で開きます。

この時、BufferedReaderのインスタンスは、tryブロックの終了時に自動的にクローズされます。

そして、ファイルから一行ずつ読み込み、それをコンソールに出力します。

もしIOExceptionが発生した場合は、catch節でそれを捕捉し、エラーメッセージをコンソールに出力します。

実際にこのコードを実行すると、指定したパスのファイルの内容がコンソールに出力されます。

もしファイルが見つからない場合や読み込みに失敗した場合は、エラーメッセージが表示されます。

また、tryブロックが終了すると、BufferedReaderのインスタンスが自動的にクローズされるため、リソースのリークを防ぐことができます。

●Java例外処理の注意点とベストプラクティス

Javaプログラミングにおける例外処理は非常に重要な部分であり、この記事ではその注意点とベストプラクティスに関して深く掘り下げていきます。

例外処理はプログラムが想定外の事態に遭遇した際に、それを適切に捉えて適切な対応を行うことを目指します。

それでは、具体的な注意点とベストプラクティスについて見ていきましょう。

○適切な例外の選択

Javaでの例外処理を行う際、まず心掛けるべきは、適切な例外を選択することです。

Javaにはいくつかの例外クラスがありますが、その中から最も適したものを選ぶことが重要です。

たとえば、以下のコードはNullPointerExceptionを適切にキャッチしています。

このコードではstrnullの場合にNullPointerExceptionがスローされ、それがキャッチされる仕組みになっています。

try {
    String str = null;
    System.out.println(str.length());
} catch (NullPointerException e) {
    System.out.println("nullへの参照が発生しました。");
}

このコードを実行すると、”nullへの参照が発生しました。”と表示されます。

このように、適切な例外の選択はコードの可読性や保守性を向上させることができます。

○例外の正確な伝播

例外の伝播は、例外が発生した箇所からキャッチされるまでのフローをきちんと設計することを指します。

適切な例外の伝播を行うことで、プログラムが予期せぬエラーによりクラッシュするのを防ぐことが可能となります。

例えば、次のコードは数値変換の例外を捉えて上位のメソッドに再スローする実例です。

public static void main(String[] args) {
    try {
        convertStringToInteger("test");
    } catch (NumberFormatException e) {
        System.out.println("数値変換エラーが発生しました。入力値を確認してください。");
    }
}

public static void convertStringToInteger(String input) throws NumberFormatException {
    int result = Integer.parseInt(input);
}

このコードを実行すると、”数値変換エラーが発生しました。入力値を確認してください。”と表示されます。

このように例外の伝播を適切に行うことで、プログラムの安定性と保守性が向上します。

まとめ

この記事がJavaでの例外処理の全てを身につけるための完全ガイドとして、皆さんの学習と実践の助けとなることを心から願っています。

そして、Javaプログラミングのスキルアップに貢献できることを嬉しく思っております。

なお、わからない点や疑問点があれば、遠慮なく質問してください。