例外

  • 例外:実行時に成功したエラーのこと。(コンパイルは成功している)
  • 例外がスローされる:例外が発生すること。
No. 例外の流れ
1 実行したプログラムに問題があるとJVMが例外をプログラムにスローする。
2 例外を発生させたプログラムはそれをキャッチする。

例外処理

  • JVMから例外がスローされた時、プログラム側でその例外に対する処理を記述していないとプログラムはそこで強制終了する。
  • そのため、アプリなど実行時に起こりうるエラーによって強制終了することを未然に防がなくてはいけない。
  • 解決策として、例外処理をプログラムに定義する。そうすることで、プログラムを強制終了することなく継続することができる。

例外クラスの分類

  • Javaの例外クラスは、以下の2種に分類される。
例外クラスの分類 説明 特徴
checked例外 DBなどJVM以外の環境が原因で発生する例外。 例外処理が必須(コンストラクタ・メソッドでthrows指定が必須)
unchecked例外 実行中のプログラムが原因で発生する例外やメモリ不足などプログラムの例外処理では復旧できない例外。 例外処理が任意(コンストラクタ・メソッドでthrowsの省略が可能)

例外クラス

例外クラス 説明
Throwableクラス 例外クラスのルートクラス。
Errorクラス Throwableクラスのサブクラス。メモリ不足などJVM環境で発生するエラーを表現。アプリ出た対応するのは困難なので、このクラス、およびこのサブクラスの例外処理は任意。(unchecked例外
Exceptionクラス Throwableクラスのサブクラス。アプリケーションで発生するエラーを表現。処理結果や発生する可能性のある例外クラスがなんであるかによって例外処理が必須、任意と変わってくる。(ExceptionのサブクラスであるRuntimeExceptionクラスとそのサブクラスはunchecked例外。RuntimeExceptionクラス以外のExceptionのサブクラスはchecked例外。)

※RuntimeException:実行エラー

  • つまり、プログラムでは解決できない例外クラスErrorは、例外処理は任意。(原因追及が困難なので)
  • また、プログラムの実行エラーで発生する例外クラスRuntimeExceptionは、例外処理は任意。(原因追及が困難なので)
  • 最後に、プログラムの実行エラーで発生する例外クラスRuntimeException以外のExceptionのサブクラスは、例外処理が必須。(エラーパターンが限られているため)

独自の例外クラスを定義

  • 定義:[アクセス修飾子] class クラス名 extends Exception {}
  • 一般的に、publicを指定する。
  • Exception, その親クラスのThrowableクラスのメソッドをを使用できる。
Throwableの主なメソッド 機能
void printStackTrace() エラーを追跡し、エラー発生箇所を出力。
String getMessage() エラーメッセージを取得。

例外処理 (方法)

  • 例外処理のやり方は、以下の2種類
    • try-catch-finallyブロック
    • throws

try-catch-finallyブロック

  • 定義:try {} catch(例外クラス 変数) {} finally{}
  • tryのみだとコンパイルエラー
  • try-catch, try-finally, try-catch-finallyなどの組み合わせで定義することができる
ブロック 説明
try 例外が発生しそうな箇所を囲む。
catch 例外が発生した時の処理を定義する。JVMからスローされた例外オブジェクトを引数で受け取り、その例外に応じて処理を変えることができる。複数定義可能。複数定義する場合は、例外クラスのサブクラス側から定義すること。
finally 例外が発生してもしなくても必ず実行したい処理を定義する。
  • 4週目でSystem.out.print(num[i]);のところでJVMが例外をスローする。
  • それをプログラム側でcatchしていることがわかる。
  • これで、プログラムが強制終了することなく、持続できている。
Training.java
//実行クラス
class Training {
    public static void main(String[] args){
        int[] num = {10, 20, 30};

        for(int i = 0; i < 5; i++){
            try{
                System.out.print(num[i]);
                System.out.println((":" + (i + 1) + "回目のループ"));
            } catch(ArrayIndexOutOfBoundsException e){
                System.out.println("エラー:配列のindexをこえたものをしています。");
            }
        }
        System.out.println("終了");
    }
}
//出力結果:
//10:1回目のループ
//20:2回目のループ
//30:3回目のループ
//エラー:配列のindexをこえたものをしています。
//エラー:配列のindexをこえたものをしています。
//終了
  • finallyは、例外が発生してもしなくても必ず実行していることがわかる。
  • finallyは主に、ファイルのclose処理などに用いられる。
Training.java(finallyの追加)
//実行クラス
class Training {
    public static void main(String[] args){
        int[] num = {10, 20, 30};

        for(int i = 0; i < 5; i++){
            try{
                System.out.print(num[i]);
                System.out.println((":" + (i + 1) + "回目のループ"));
            } catch(ArrayIndexOutOfBoundsException e){
                System.out.println("エラー:配列のindexをこえたものをしています。");
            } finally{
                System.out.println("finallyの実行");
            }
        }
        System.out.println("終了");
    }
}
//出力結果:
//10:1回目のループ
//finallyの実行
//20:2回目のループ
//finallyの実行
//30:3回目のループ
//finallyの実行
//エラー:配列のindexをこえたものをしています。
//finallyの実行
//エラー:配列のindexをこえたものをしています。
//finallyの実行
//終了

catchについて

No. 要点
1 catchブロックは複数定義可能。
2 引数に指定した例外クラス間に継承関係がある場合は、サブクラスから定義すること。でないとコンパイルエラー。(親クラスを上に定義すると例外オブジェクトが自動でキャストされ、吸収されてしまうため。(引数の型変換と同じ))
3 SE7から、マルチキャッチができるようになった。(継承関係のない複数の例外クラスを引数に指定でき、まとめて例外をキャッチできる。)
4 継承関係があるクラスは、マルチキャッチできない。コンパイルエラー。
  • No.2の例を示す。
//ok
} catch(NullPointerException e) {
    ...
} catch(Exception e){}

//コンパイルエラー
} catch(Exception e){
    ....
} catch(NullPointerException e){}
  • No.3の例を示す。
  • |で例外クラスを区切ることで継承関係のない複数の例外クラスを引数に指定することができる。
try{
    System.out.println(10 / 0);
    FileReader rf = new FileReader("sample.txt");
} catch(FileNotFoundException | ArithmeticException e) {
    System.out.println(e.getMessage());
}
  • No.4の例を示す。
  • FileNotFoundExceptionIOExceptionは継承関係がある。
// コンパイルエラー
try{
    ...
} catch(FileNotFoundException | IOException | ArithmeticException e) {}

throws, throw

throws

  • 例外処理の一種。
  • 例外が発生する可能性のあるメソッドに用いる。
  • 各メソッド内で、重複する例外処理を楽にするもの。例外処理が重複する前提なのでsがつく。
  • メソッド内でthrows指定された例外クラスのオブジェクトが発生した場合、それをreturnする。
  • 定義:[アクセス修飾子] 戻り値の型 メソッド名(引数) throws 例外クラス名 {}
No. 要点
1 発生した例外と異なる例外オブジェクトを指定した場合、returnされることなく、プログラムは強制終了する。
2 throwsの場合、try-catch-finallyとは異なりメソッド内で例外が発生した時点でメソッドの処理は終了し、例外をreturnする。

以下では、throwsを用いた例を示す。
loop()メソッド内でArrayIndexOutOfBoundsExceptionエラーが発生した場合、その例外オブジェクトをmain()メソッドにreturnしていることがわかる。main()メソッド側でスローされた例外をcatchしていることもわかる。

Training.java
//設計図クラス
class Test {

    void loop() throws ArrayIndexOutOfBoundsException{
        int[] num = {10, 20, 30};
        for(int i = 0; i < 5; i++){
            System.out.println(num[i]);
        }
    }
}

//実行クラス
class Training {
    public static void main(String[] args){
        try{
            Test obj = new Test();
            obj.loop();
        } catch(ArrayIndexOutOfBoundsException e){
            System.out.println("エラー:配列のindexをこえたものをしています。");
        }
        System.out.println("終了");
    }
}
//出力結果:
//10
//20
//30
//エラー:配列のindexをこえたものをしています。
//終了

どんな時にthrowsを用いるのか

  • 上記コードを用いて説明する。
  • throwsを用いない場合、try-catch-finallyを用いて例外処理を行うことになる。
  • 仮に、loop()メソッドと処理内容が全く同じloop2()メソッドが定義されているとする。
  • この場合、ArrayIndexOutOfBoundsExceptionをcatchする同じtry-catch-finallyをloop(), loop2()メソッドの両方で定義する必要がある。
  • これは、コードが冗長になってしまう。
  • そのため、各メソッドで重複する例外処理を楽に定義するためにthrowsが用いられる。

unchecked例外では、throwsが省略できる

unchecked例外にthrowsを用いる場合、例外処理が任意なためthrowsを省略することができる。(コンパイル、実行が問題なく行える)

No. unchecked例外の場合、throwが省略可能な理由
1 unchecked例外が発生した場合、JVMはその例外の種類を問わず、throwsがなくてもプログラムの呼び出し元にreturn(スロー)するようになっているため。
1-1 それをプログラム上でcatchすれば、持続実行が可能になる。(例外処理)
1-2 プログラム上でcatchしなければ、最終的にJVMがcatchし、エラーのトレースを出力する。ただし、プログラム上でエラーをcatchしなければ、プログラムが強制終了する。

以下は、throwsを省略した場合を示す。
loop()メソッドで例外が発生し、throwsがなくてもJVMによってそれをloop()メソッドの呼び出し元であるmain()メソッドにスローしていることがわかる。

Training.java
//設計図クラス
class Test {

    void loop(){
        int[] num = {10, 20, 30};
        for(int i = 0; i < 5; i++){
            System.out.println(num[i]);
        }
    }
}

//実行クラス
class Training {
    public static void main(String[] args){
        try{
            Test obj = new Test();
            obj.loop();
        } catch(ArrayIndexOutOfBoundsException e){
            System.out.println("エラー:配列のindexをこえたものをしています。");
        }
        System.out.println("終了");
    }
}
//出力結果:
//10
//20
//30
//エラー:配列のindexをこえたものをしています。
//終了

※checked例外の場合、throwsまたはtry-catch-finallyによる例外処理がなければ、コンパイルエラーになる。(例外処理が必須なので)

ライブラリを利用する際の注意点

No. 確認すること
1 ライブラリを利用する場合、使用するコンストラクタ・メソッドにthrowsが指定されているかを確認する。
2 throwsが指定されている場合、その例外クラス名の継承関係を見て、呼び出し元のプログラム側で例外処理(try-catch-finally)が任意・必須かを判断する。

※throwsが定義されていてもそれに指定されている例外クラスがunchecked例外の場合もあるので、その場合は呼び出し元のプログラム側でtry-catch-finallyを省略することができる。

  • ライブラリを用いる例を示す。
  • IntegerクラスparseInt()メソッドは、int java.lang.Integer.parseInt(String s) throws NumberFormatException。throwsはあるがNumberFormatException例外クラスはunchecked例外なので、呼び出し元のプログラムで例外処理(try-catch-finally)が任意。
  • FileReaderクラスのコンストラクタは、FileReader.FileReader(String fileName) throws FileNotFoundException。throwsがあり、FileNotFoundException例外クラスはchecked例外であるため、呼び出し元のプログラムで例外処理(try-catch-finally)が必須。
Training.java
import java.io.*;

//実行クラス
class Training {
    public static void main(String[] args){
        //例外処理は任意
        int i = Integer.parseInt("10");

        //例外処理は必須
        FileReader r = new FileReader("sample.txt"); //コンパイルエラー
    }
}

throw

  • 例外処理ではない。
  • 意図的にエラーを発生(スロー)する際に用いる。(JVMがエラーをスローするだけでなく、自分でスローしたい時に用いる)
  • デフォルトの例外クラスや独自に定義した例外クラスをインスタンス化した例外オブジェクトに対して指定する。
  • 定義:throw 例外オブジェクト;

独自の例外クラスを定義する。
checkAge()メソッドでelseの場合、独自例外クラスの例外をインスタンス化し、throwで独自例外を意図的に発生させる。
それをthrowsによって、プログラムの呼び出し元(main()メソッド)にreturnしている。

Training.java
//例外クラス(独自)
class MyException extends Exception {
    private int age;
    public void setAge(int age){ this.age = age; }
    public int getAge(){ return this.age; }
}

//実行クラス
class Training {
    public static void main(String[] args){
        try{
            int age = - 1;
            checkAge(age);
        } catch(MyException e){
            System.out.println("不正な数値となっています。:" + e.getAge());
        }
    }

    public static void checkAge(int age) throws MyException{
        if(age > 0){
            System.out.println("あなたの年齢:" + age);
        } else {
            MyException e = new MyException(); //例外クラスをインスタンス化
            e.setAge(age);
            throw e; //例外オブジェクトを意図的にスロー
        }
    }
    
}
//出力結果:不正な数値となっています。:-1

ここの独自例外クラスは、Exceptionクラスを継承しており、RuntimeExceptionクラス以外の例外クラスに含まれるので、これを用いる場合(メソッドで)は、throwsが必須で、さらにプログラム呼び出し元で例外処理(try-catch-finally)が必須になる。

throws指定メソッドのオーバーライド

throws指定のメソッドをオーバーライドする場合、以下のルールがある。

No. ルール
1 親メソッドのthrowsは省略可能。(省略した場合、親メソッドのthrows指定と同じものになる。)
2 親メソッドのthrowsに指定している例外クラスと同様、またはそのサブクラスでオーバーライド可能。
3 親メソッドのthrowsに指定している例外クラスのスーパークラスでオーバーライドはコンパイルエラー。
4 親メソッドのthrowsに指定している例外クラスとは継承関係にない例外クラスでのオーバーライドはコンパイルエラー。
5 RuntinmeException例外クラスの場合、親メソッドのthrowsに指定している例外クラスに関係なくオーバーライドできる。
import java.io.*;

class Super { void method() throws IOException {} }

class SubA extends Super { void method() {} }
class SubB extends Super { void method() throws FileNotFoundException {} }
class SubC extends Super { void method() throws Exception {} } //コンパイルエラー
class SubD extends Super { void method() throws ClassNotFoundException {} } //コンパイルエラー
class SubE extends Super { void method() throws RuntimeException {} }

※FileNotFoundException例外クラス:IOException例外クラスのサブクラス。
※ClassNotFoundException例外クラス:IOException例外クラスと継承関係のない例外クラス。