Java
 Computer >> コンピューター >  >> プログラミング >> Java

Javaの例外(Exception)とは?基本概念から例外処理の方法まで徹底解説

Java開発者にとって、Javaの例外と例外処理に関する知識は必須です。

本チュートリアルでは、Javaプログラミングにおいてすべてのエンジニアが押さえておくべき基礎知識を解説します。まずは「Javaの例外とは何か」というところから始めましょう。

Javaの例外とは

Javaプログラムは、実行中に何らかの問題に遭遇し、突如として強制終了することがあります。このような問題を例外(Exception)と呼びます。

優れたプログラマーは、実行時に発生し得るエラーを予測し、例外が起きた場合でもプログラムが別の経路で動作を継続できるように設計します。このような実践が例外処理(Exception Handling)です。

ここで「そもそも、なぜ例外処理が必要なのか?例外を投げないプログラムを書けばいいのでは?」と思った方もいるでしょう。

なぜ例外処理が必要なのか

実際のところ、例外を一切投げないプログラムを書くのは、想像以上に困難です。多くの場合、これらのエラーはプログラマーの制御範囲外で発生します。

たとえば、ユーザーからの入力を受け付けるプログラムは、無効な入力によって例外が発生しやすくなります。また、外部ファイルを読み込む処理でも、ファイルが第三者によって移動・リネーム・削除される可能性があり、プログラマーはそれを事前に把握できません。

このような状況でも、プログラムが強制終了せず、適切に例外を処理して動作を続けられるようにする必要があるのです。

Java例外の階層構造

Javaにおけるすべての例外は、Exceptionクラスの子クラスである必要があります。そしてExceptionクラス自体は、Throwableクラスの子クラスです。

Exceptionクラスの主要なサブクラスとしては、RuntimeExceptionIOExceptionの2つが挙げられます。

ExceptionとErrorの違い

Throwableクラスのもう一つの子クラスとして、Errorクラスが存在します。しかし、Errorは例外とは異なるものです。

Errorは、JVMが実行中に遭遇する問題を示します。これらの問題は通常深刻であり、復旧が困難です。メモリリークやライブラリの互換性問題などが、Errorの代表的な原因です。

JavaのErrorの例としては、StackOverflowErrorOutOfMemoryErrorが挙げられます。

チェック例外と非チェック例外

Javaの例外は、大きくチェック例外(Checked Exception)非チェック例外(Unchecked Exception)の2種類に分類できます。

チェック例外とは、コンパイル前にプログラム内で処理されることが求められる例外です。これらの例外が処理されていない場合、Javaコンパイラはプログラムをコンパイルしません。そのため、「コンパイル時例外」とも呼ばれます。代表的な例がIOExceptionです。

一方、非チェック例外とは、コンパイラがコンパイル時に無視する例外です。プログラム内で処理しているかどうかはコンパイル時に問われません。例外処理が強制されないため、プログラムはRuntimeExceptionによって強制終了する可能性があります。

RuntimeExceptionクラスを継承するすべてのクラスは非チェック例外です。代表例として、NullPointerExceptionArrayIndexOutOfBoundsExceptionがあります。

Exceptionクラスでよく使われるメソッド

JavaのExceptionクラスで頻繁に使われるメソッドをいくつか紹介します。

  1. getMessage:発生した例外の詳細情報を含むメッセージを返します。
  2. printStackTrace:発生した例外のスタックトレースを出力します。
  3. toString:クラス名と、getMessageメソッドが返すメッセージを連結した文字列を返します。

例外の処理方法

それでは、Javaで例外を処理する具体的な方法を見ていきましょう。

try-catch

Javaではtry-catchブロックを使って例外を捕捉し、適切に処理できます。

この構文では、例外が発生しやすいコードをtryブロック内に記述し、catchブロックで投げられた例外を捕捉して、あらかじめ用意したロジックに従って処理します。

try-catchブロックの基本的な構文は以下の通りです。

try {
    //例外が発生する可能性のあるコード
}
catch(Exception e) {
    //エラー処理のロジック
}

このアプローチにより、例外が発生してもプログラムは実行を停止せず、適切に処理されます。

次に、JavaプログラムでFileReaderクラスがスローするIOExceptionを処理する例を見てみましょう。

例:

import java.io.FileReader;

public class TryCatchBlockExample {

    public static void main(String[] args) {

        try {
            FileReader file = new FileReader("source.txt");
            file.read();
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
}

この例では、単一のcatchブロックを使って、FileReaderクラスのインスタンス化時にスローされるFileNotFoundExceptionと、read()メソッドがスローするIOExceptionの両方を処理しています。

これらの例外はどちらもExceptionクラスの子クラスです。

また、複数のcatch文を使って、1つのtry文内のコードがスローする異なる種類のエラーを個別に捕捉することも可能です。先ほどの例では、FileNotFoundException用とIOException用に、それぞれ別のcatchブロックを用意できます。

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchBlockExample {

    public static void main(String[] args) {

        try {
            FileReader file = new FileReader("source.txt");
            file.read();
            file.close();
        }
        catch(FileNotFoundException e) {
            e.printStackTrace();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}

スローされた例外が最初のcatch文で処理される例外と一致した場合、その例外は最初のcatch文内のロジックによって処理されます。

一致しなかった場合、例外は次のcatch文に渡されます。catch文が3つ以上ある場合は、例外の型に一致するcatch文に到達するまで、この処理が順番に繰り返されます。

ただし注意点があります。FileNotFoundExceptionIOExceptionのサブタイプであるため、2番目のcatch文でFileNotFoundExceptionを捕捉することはできません。例外は必ず最初のcatch文で処理され、2番目のcatch文には到達しません。

ポイント: try文を使用する場合は、最低1つのcatch文を併記することが必須です。

finally

try-catchブロックで例外を捕捉する際、例外の発生有無にかかわらず実行したい処理がある場合があります。そのようなケースでは、try-catchの代わりにtry-catch-finallyブロックを使用します。

finally文内のコードは、例外が発生したかどうかにかかわらず必ず実行されます。finally文は、try-catch-finallyブロックの最後に必ず配置します。

たとえば、FileReaderクラスでファイルを読み込む場合、例外の有無に関係なく、処理の最後に開いたファイルを閉じることが重要です。これを保証するために、ファイルを閉じるコードをfinally文の中に記述します。

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchFinallyBlockExample {

    public static void main(String[] args) {
        FileReader file = null;
        try {
            file = new FileReader("source.txt");
            file.read();
        }
        catch(FileNotFoundException e) {
            e.printStackTrace();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        finally {
            file.close();
        }
    }
}

しかし、上記のコードをコンパイルしようとしても、未処理のIOExceptionがあるためコンパイルできません。これは、FileReaderクラスのclose()メソッドもIOExceptionをスローする可能性があるためです。そこで、この部分を以下のように別のtryブロックで囲む必要があります。

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchFinallyBlockExample {

    public static void main(String[] args) {
        FileReader file = null;

        try {
            file = new FileReader("source.txt");
            file.read();
        }
        catch(FileNotFoundException e) {
            e.printStackTrace();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        finally {
            try {
                file.close();
            }
            catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}

throws

Javaでthrowsキーワードを使ってエラーを処理する方法はシンプルです。実はこのアプローチでは、例外が発生した箇所で直接処理することはありません。代わりに、現在のメソッドから呼び出し元のメソッドへ例外を投げます。すると、エラーへの対処は外側のメソッドの責任になります。

メソッドから例外を投げるには、そのメソッドが対象の例外をスローする可能性があることを宣言するだけです。FileReaderクラスがスローするIOExceptionを、このアプローチで処理する例を見てみましょう。

例:

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

public class ThrowsExample {

    public void readFile throws IOException {
        FileReader file = new FileReader("source.txt");
        file.read();
        file.close();
    }
}

throw

これまで紹介した他のアプローチとは異なり、throwキーワードはエラーを処理するためのものではありません。しかし、多くの人がthrowthrowsを混同するため、ここで合わせて解説しておきます。

throwキーワードは、例外を明示的に発生させるために使用します。新しくインスタンス化した例外や、メソッド内で捕捉した例外を投げることができます。

public class ThrowExample {

    public void invalidate(int amount) throws Exception {
        if (amount < 500) {
            throw new Exception("Amount not sufficient");
        }
    }
}

ユーザー定義例外

Javaに組み込みの例外を使用するだけでなく、独自の例外を定義することもできます。定義する例外は、チェック例外にも非チェック例外にもできます。新しいチェック例外を作成するには、Exceptionクラスを継承します。

非チェック例外を作成する場合は、RuntimeExceptionクラスを継承します。

次のコード例では、ユーザー定義のチェック例外を作成しています。

public class InvalidLengthException extends Exception {
    private int length;
    private String message;

    public InvalidLengthException(int length, String message) {
        this.length=length;
        this.message=message;
    }

    public int getAmount() {
        return this.length;
    }

    public String getMessage() {
        return this.message;
    }
}

この例外は、次のようにプログラムのロジック内で使用できます。

public class InputChecker {

    private int minLength;
    private int maxLength;

    public InputChecker(int minLength, int maxLength) {
        this.minLength=minLength;
        this.maxLength=maxLength;
    }

    public void checkStringLength(String strInput) throws InvalidLengthException {
        int strLength = strInput.length();
        if (strLength < minLength) {
            throw new InvalidLengthException(strLength, "Input should have minimum "+minLength+" characters");
        }
        else if (strLength > maxLength){
            throw new InvalidLengthException(strLength, "Input should have maximum "+maxLength+" character");
        }
    }
}

InputCheckerクラスで文字列の長さをチェックすると、文字列の長さが最小長未満または最大長超過の場合にInvalidLengthExceptionがスローされます。

public class Main {

    public static void main(String[] args) {
        InputChecker ic = new InputChecker(2, 7);
        try {
            ic.checkStringLength("longer than the maximum length");
        }
        catch(InvalidLengthException e) {
            e.printStackTrace();
        }
    }
}

上記のコードを実行すると、InvalidLengthExceptionがスローされ、次のような出力が得られます。

InvalidLengthException: Input should have maximum 7 character
    at InputChecker.checkStringLength(InputChecker.java:17)
    at Main.main(Main.java:6)

まとめ

本チュートリアルでは、Javaの例外について簡潔に紹介しました。例外とは何か、そしてJavaプログラムで例外をどのように処理するのかについて、しっかりと理解を深められたなら幸いです。

  1. JSPで例外を処理する方法:c:catchタグの使い方を徹底解説

    JSPアプリケーションを開発していると、実行時エラー(例外)が発生することがあります。JSTL(JSP Standard Tag Library)には、こうした例外を安全に捕捉するための<c:catch>タグが用意されています。このタグは、タグの本文内で発生したあらゆるThrowableオブジェクトを捕捉し、指定した変数に格納することができます。これにより、ページ全体がクラッシュするのを防ぎ、ユーザーにとって分かりやすい形でエラー処理を行うことが可能になります。c:catchタグの属性<c:catch>タグには以下の属性があります。属性説明必須デフォルト値var本文内の

  2. 【初心者向け】C++の例外処理の仕組みを徹底解説!try・catch・throwの基本から独自例外の作成まで

    C++における例外処理(Exception Handling)とは、プログラムの実行中に発生するエラー(ランタイムエラー)を適切に処理するための仕組みです。例外とは、実行時に送出されるイベントのことを指し、C++ではすべての標準例外が std::exception クラスを基底クラスとして定義されています。 もし例外を捕捉しなければ、プログラムは例外メッセージを出力した後に異常終了してしまいます。例外はハンドリング可能なランタイムエラーであり、適切に処理することでプログラムの堅牢性を大きく向上させることができます。 標準例外クラスの階層構造 C++標準では、例外は <exception&