メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

Javaの勉強(4):例外処理

プログラム実行時の不具合を例外という.例外をそのままにしてしまうと,プログラムが強制終了してしまいよくない.例外にたいする処理のことを「例外処理」という.Javaでどのように例外処理が行われているかについてまとめておく.

例外処理の方法

Javaでは例外が起きると,その例外に応じた「例外のインスタンス」が生成される.よくあるのがStringIndexOutOfBoundsExceptionArrayIndexOutOfBoundsExceptionといったものがあり,これらは「例外クラス」として定義されている.例外処理はこの例外のインスタンスをどのように処理するか,と言い換えることができる.
例外処理には2つの方法がある.

  • try-catch-finally

例外のインスタンスをそのコード内で処理する方法.同じバリエーションとしてtry-with-resourcesという方法もある.

  • throws

例外のインスタンスを呼び出し元に返し,呼び出しもとで処理してもらう方法.例外はその処理の中では処理をしない.

例外クラス

どういった例外が生じるか,は例外クラスとして定義されている.例外クラスは,Throwableというインターフェースを実装しているExceptioクラスをスーパークラスに持つ.例外クラスは大きく2種類に分けられる

  • RuntimeExceptionクラスとそのサブクラス

実行時に起きる例外.例えば配列の範囲外参照などがこちらに当たる.コンパイル時にコンパイラにチェックされない例外で,「非チェック例外」という.

  • RuntimeExceptionクラス以外のExceptionのサブクラス.

ファイルのオープン時に起きうるファイルが見つからないという例外など,起きることが予測できる例外.例外処理が必要で,コンパイル時に例外処理がなされているかどうかチェックされる.そのため「チェック例外」と呼ばれる.

try-catch-finally

処理の中で起きた例外を,そのコード(メソッドの内部)で処理する方法である.例外が発生しそうな部分をtryのブロックで囲み,生じた例外をcatchで処理をする方法である.文法は以下の通り.
>|java
try {
なんかの処理
} catch (起きうる例外1 e){
例外1に対する処理
eは例外1のインスタンスを入れた参照変数
} catch (起きうる例外2 e){
例外2に対する処理
} ....
} finally {
tryブロック/catchブロックの処理が終わった場合に行う処理を書く
}
|

コード例を示す.

public class ExceptionMain {
    public static void main(String[] args) {
        String str = "abc";

        printCharAt(str, 1);
        printCharAt("12345", 6);
    }

    public static void printCharAt(String str, int position){
        try{
            System.out.println(str.charAt(position));
        } catch (StringIndexOutOfBoundsException e){
            System.out.println("例外発生");
        } finally {
            System.out.println("処理完了");
        }
    }

    //例外処理なしで同様の処理をするメソッドを用意
    public static void printCharAtWithdoutTry(String str, int position) {
        System.out.println(str.charAt(position));
    }
}

printCharAtを使った場合の結果

実行結果
b
処理完了
例外発生
処理完了

Process finished with exit code 0

printCharAtWithoutを使った場合の結果

b
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 6
	at java.lang.String.charAt(String.java:658)
	at exception.ExceptionMain.printCharAtWithdoutTry(ExceptionMain.java:25)
	at exception.ExceptionMain.main(ExceptionMain.java:11)

Process finished with exit code 1

Stringのある位置の文字を標準出力に表示するという2つのメソッドを準備した.このメソッドではstrの範囲外のpositionが渡されると,StringIndexOutOfBoundsExceptionという例外が生じる.
printCharAtWithoutを使った場合をみてみると,例外が発生した後プログラムが強制終了していることがわかる(Process finishied with exit code 1).

try-catch-finallyを使ったメソッドの結果を見てみよう.
printCharAtの中にtry-catch-finallyが使われている.tryのブロック内部にSystem.out.println(str.charAt(position))という処理が入っている.tryブロック中で生じた例外StringIndexOutOfBoundsExceptionは,catchで捕獲され,ブロックの中で処理がされる.よって,こちらを使った場合はプログラムが強制終了していない(Process finished with exit code 0).このように,try-catch-finally構文を使う.

tryブロック

例外発生の範囲を指定する.このブロック内で生じた例外のみが例外処理の対象となる.例外が起きたときはそこからcatchブロックへ飛ぶ.
tryブロックの残りの処理は飛ばされ実行されないことに注意.例外が起きなかった場合はtryブロック内部は全て実行される.

catchブロック

tryブロックで例外が生じた場合は実行される.catchブロックは,生じた例外クラスのインスタンスを引数に取ることができる.例外のインスタンスが引数の型に合わない場合はブロックは実行されない.またcatchブロックは複数用意することができる.例外が起きた場合はcatchブロックが上から順番に引数が受け取れるものかどうかをチェックし,初めに受け取れたブロックが実行される.その後ろのブロックは無視される.
したがって,なるべく細かい例外クラスからcatchブロックに書いていく必要がある.例外クラスの大元であるExceptionクラスをcatch分の一番上に書いてしまうと,そのブロックで全ての例外インスタンスが引数として渡ってしまい,後ろのブロックが処理されなくなるので注意.

finallyブロック

例外が起きようと起きまいと実行されるブロック.起きない場合は,tryブロックの処理が完了してからfinallyブロックが実行される.例外が起きた場合はcatchブロック内部が実行された後にfinallyブロックが実行される.catchブロックの中にreturnを書く場合があるが,その場合でもreturnの前にfinallyブロックが実行されてからreturnされる.

catchブロックとfinallyブロックはどちらか一方があればよく,そのためfinallyブロックは省略されることが多い.

throws

生じた例外のインスタンスを,メソッドの呼び出し元に返してしまう処理.メソッドの中では例外処理を行わず,メソッド呼び出し元で例外処理を行う必要がある.次のように書く

datatype method() throws ExceptionClassName {
    do something;
}

コード例を以下に示す.

public class ExceptionMain {
    public static void main(String[] args) {
        String str = "abc";

        try{
            //printCharAt(str, 1);
            printCharAt("12345", 6);
        } catch (StringIndexOutOfBoundsException e){
            e.printStackTrace();
            // 例外処理の情報をprintするメソッド
        } finally {
            System.out.println("main finally");
        }

        System.out.println("finish main");
    }

    public static void printCharAt(String str, int position) throws StringIndexOutOfBoundsException {
        System.out.println(str.charAt(position));
        System.out.println("finish printCharAt method");
    }
}

実行結果は以下の通り

java.lang.StringIndexOutOfBoundsException: String index out of range: 6
	at java.lang.String.charAt(String.java:658)
	at exception.ExceptionMain.printCharAt(ExceptionMain.java:30)
	at exception.ExceptionMain.main(ExceptionMain.java:9)
main finally
finish main

Process finished with exit code 0

printCharAtメソッドにthrowsをつけStringIndexOutOfBoundsException例外を呼び出し元に投げるように変更した.そのため,try-catch文をmainメソッドの中に書き,mainメソッド内部で例外処理を行うように変更する.

生じる例外が複数ありうる場合は,throwsの後に複数の例外クラスを,で区切って書くことができる.指定したクラスにサブクラスがある場合,そのサブクラスの例外にも対応する.

throw

例外を発生させる時に使う方法.

throw 例外クラスのインスタンス;

コード例を以下に示す.

public class ExceptionMain {
    public static void main(String[] args) {

        try {
            fileException();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            System.out.println("main finally");
        }

        System.out.println("finish main");
    }

    public static void fileException() throws FileNotFoundException{
        System.out.println("fileException");
        throw new FileNotFoundException("File not found!");
    }

}

実行結果は以下の通り.

fileException
java.io.FileNotFoundException: File not found!
	at exception.ExceptionMain.fileException(ExceptionMain.java:21)
	at exception.ExceptionMain.main(ExceptionMain.java:9)
main finally
finish main

fileExceptionメソッドでFileNotFoundExceptionインスタンスを生成し,それを呼び出し元に投げている.mainでtry-catchを使い例外処理をしている.
チェック例外を投げる場合は,throwsで必ずどの例外がthrowされるかを指定しておく必要があり,ない場合はコンパイルエラーとなるので注意.

例外クラスの定義

自分で例外クラスを作成する方法を述べる.既存の例外クラスを継承していれば,それは例外クラスになる.例外クラスを自作する場合,名前は***(英語で例外の説明) + Exceptionといった名前にするのが一般的.コード例を以下に示す.

// MyException.java
public class MyException extends Exception{
    public MyException(String message) {
        super(message);
    }

    public MyException(){
        this("");
    }

    public void sayMessage() {
        System.out.println("This is MyException!");
    }
}
// ExceptionMain.java
public class ExceptionMain {
    public static void main(String[] args) {

        try {
            myMethod();
        } catch (MyException e) {
            e.printStackTrace();
            e.sayMessage();
        } finally {
            System.out.println("main finally");
        }

        System.out.println("finish main");
    }

    public static void myMethod() throws MyException {
        System.out.println("myMethod");
        throw new MyException("new MyException");
    }

}

実行結果は以下の通り.

myMethod
exception.MyException: new MyException
	at exception.ExceptionMain.myMethod(ExceptionMain.java:22)
	at exception.ExceptionMain.main(ExceptionMain.java:9)
This is MyException!
main finally
finish main

Process finished with exit code 0

新たに定義した例外クラスMyExceptionインスタンスが生成され,それに応じた例外処理が行われていることがわかる.