Ch8.例外クラス

Javaでは例外処理を記述しないとコンパイルエラーになるメソッドが存在する。 そうしたメソッドが発生させる例外をchecked例外と呼ぶ。逆に、例外処理を記述しないくても良いメソッドが発生させる例外をunchecked例外と呼んでいる。

例えばアプリケーション側で対応するのが困難なエラー(メモリ不足などのJavaの実行環境に起因する問題)はThrowableクラス以下のErrorクラスの例外オブジェクトが発生する。こちらはunchecked例外となっている。 一方、プログラムの中のメソッドで発生する例外全般で、かつ(発生頻度が低い等の理由で)例外処理が任意となっている例外はThrowable > Exceptionクラス以下のRuntimeExceptionクラスの例外が発生する。逆に(頻繁に発生することが見込まれる等の理由で)かならず例外処理を書かなくてはいけない例外はThrowable > Exception以下のRuntimeExceptionクラス以外の例外が発生する。

因みにchecked例外は他の言語では採用されない傾向があり、Kotlinなどでも採用されていない模様。

import java.io.FileReader;
class MyException extends Error{
}
public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver"); //'Exception in thread "main" java.lang.Error: Unresolved compilation problem: 処理されない例外の型 FileNotFoundException'
    }
}

↑checked例外が発生するメソッドで例外処理を書かなかった場合に発生するコンパイルエラー

なお、自分で例外クラスを定義することもできる。その場合、Throwable > Exceptionクラスを引き継ぐ。Throwableクラスにはエラーメッセージを取得したりスタックトレースを取得したりするメソッドが用意されており、これを利用してオリジナルの例外を定義することが出来る。

例外処理を記述する最もシンプルな方法はtry-catch-finallyを利用する方法である。 finallyでは、例外の発生にかかわらず行いたい処理を記述できるのでDBのコネクションの終了やファイルのクローズなどを書いておく。

catch(rescue)においてRubyでは「例外クラスのサブクラス側から順に記述しないと例外を握りつぶしてしまう」という特性を持っていた。Javaではもっと高機能で「例外クラスのサブクラス側から記述しないとコンパイルエラーになる」らしい。

public class Ch8 {

    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        try {
            FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver");
        } catch (Throwable e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch(Exception e) {
            e.printStackTrace(); //'Exception の到達不可能な catch ブロック。すでに Throwable の catch ブロックによって処理されています'
        }

    }
}

例外クラスの種類に応じてcatchをいちいち書くのは面倒なので、マルチキャッチという機能が用意されている。

public class Ch8 {

    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        try {
            FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver");
        } catch (Exception |Error e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } 
    }
}

なお、先ほどの継承関係の理由から、同じマルチキャッチの中に継承関係のある例外クラスを書くことは出来ない。

       catch (Throwable |Exception |Error e) {
            //  'Exception の到達不可能な catch ブロック。すでに Throwable の catch ブロックによって処理されています'
            e.printStackTrace();
        } 

メソッド内で例外をカバーしなくても、メソッドの呼び出し元で例外を発生させて、呼び出し側で対処させる方法もある。 その場合、メソッド定義に発生する可能性のある例外を明示しておく必要がある。(言い換えるとここで明示しない例外クラスは呼び出し元に転送できない。)

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        try {
            Ch8 object = new Ch8();
        } catch (Throwable e) {
            // 'Exception の到達不可能な catch ブロック。すでに Throwable の catch ブロックによって処理されています'
            e.printStackTrace();
        } 
    }
    
    public void myMethod() throws Throwable {
        FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver");      
    }
}

checked例外を設定した場合throwableを設定しておけば、呼び出し側で必ずcatchする必要はない。try-catchの場合と比べて例外処理は簡素にできる。

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
            Ch8 object = new Ch8();
    }
    public void myMethod() throws Throwable {
        FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver");      
    }
}

Thorwableには継承関係にかかわらず、なんでも書けてしまう。

   public void myMethod() throws Throwable, Exception, Error {
        FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Silver");      
    }

throwsされる側で上位の例外クラスをcatchしておけば、同じクラスでなくても例外に対応できる。

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        try {
            Ch8 object = new Ch8();
            object.myMethod();
        }
        catch(Throwable e){
            System.out.println("Error");//'Error'
        }
    }
    public void myMethod() throws FileNotFoundException {
        FileReader file = new FileReader("C:\\Users\\broad\\Documents\\Java Gold");
    }
}

Rubyと同じようにraiseも可能である。throwというキーワードを使う以外は全て同じであるが、発生させた例外オブジェクトは明示的にインスタンス化する必要がある点に注意する。

class MyException extends Error{
    public void printStackTrace() {
        System.out.println("MyException!");//'MyException!'
    }

}

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        try {
            //Ch8 object = new Ch8();
            //object.myMethod();
            throw new MyException();
        }
        catch(Throwable e){
            e.printStackTrace();
        }
    }
}

throwするメソッドがchecked例外である場合、例外処理が必要となる。 なお、RuntimeExceptionの親クラスであるExceptionクラスの例外もcheked例外に該当する。メソッドのthrowsとしてchecked例外を指定する場合は呼び出し元でも処理を行わなくてはならない。

class NewClass{
    void method() throws Exception{
        throw new Exception();
    }
}

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        NewClass nc = new NewClass();
        nc.method();//'Exception in thread "main" java.lang.Error: Unresolved compilation problem: 処理されない例外の型 Exception'
    }
}

最後にメソッドがオーバーライドされたときに、継承側でどのような例外処理を設定すればよいのか検討する。 継承側では、継承元で設定された例外クラスとそのサブクラスの例外をthrowsに設定できる。

それ以外の例外を発生させることは出来ないが、RuntimeExceptionとそのサブクラスはthrowsに追加できる。 なお、親クラスで設定されたthrows対象の例外クラスはそのまま子クラスに引き継がれるので、特に明示する必要はない。

try-catch-finallyでreturnを利用するとき、挙動に少し気を付ける必要がある。

       int method() {
        try {
            System.out.println("HelloWorld");
            throw new ArrayIndexOutOfBoundsException();
            return 100;
        }catch(RuntimeException e) {
            System.out.println("Error");
            return 200;
        }
        finally {
            System.out.println("Finally");
        }
    }

上記のようなメソッドがあったとき、`returnによって値が返されるのは「finally」という表示があってからになる。 finally節はメソッドが返却される前に必ず実行されるものであり、finally節に実行を待ってから初めてreturnによる値の返却が行われる。

class NewClass{
    @SuppressWarnings("finally")
    int method() {
        try {
            System.out.println("HelloWorld");
            throw new ArrayIndexOutOfBoundsException();
        }catch(RuntimeException e) {
            System.out.println("Error");
            return 200;
        }
        finally {
            System.out.println("Finally");
            return 300;
        }
    }
}

public class Ch8 {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        NewClass nc = new NewClass();
        int a = nc.method();
        System.out.println(a);
    }
}

次にreturnの値がfinallyによって上書きされる例を見てみる。 catchで設定された返却値はfinally節によって上書きされてから返却される。よって上記プログラムの出力は以下のようになる。

HelloWorld
Error
Finally
300

当然だが、finallyは必ず実行されるもので例外が発生しなくても同じく300が返却される。

覚えておきたい例外クラス

IndexOutOfBoundsException

サブクラスにArrayIndexOutOfBoundsEceptionStringIndexOutOfBoundsEceptionを持つ。 ArrayListの配列でレンジエラーが発生した場合は親クラスである IndexOutOfBoundsExceptionが呼び出される。

IndexOutOfBoundsException (Java Platform SE 8)

IllegalStateException

メソッドを呼び出すのに適切でない状態の時、この例外クラスが発生するらしい。 いつつかうんだろう、と思っていたらこんな例を発見。

IllegalStateExceptionとは?Java初心者の勉強 | Programmer Life

ExceptionInIntializerError

staticイニシャライザ内でエラーが発生した場合のエラー。 staticイニシャライザはどこかのメソッドによって呼ばれるわけではなく、JVMが新たにクラスファイルをロードするときに実行される。(finalizeみたいなもの)

なので、ExceptionではなくError(実行環境に起因するエラー)に分類されている。

因みに、メソッドコールがループしてしまうStackOverFlowErrorとか、VMのメモリエラーが発生した場合のOutOfMemoryErrorなどはどちらもVirtualMachineErrorという(実行環境に起因する)エラークラスを親クラスに持っている。

VirtualMachineError (Java Platform SE 8)