最終更新日:2021/01/30 原本2017-

例外

プログラムを作成していると,全てが想定通りに動作することは保証出来ません. 例えば, などが起こりえます. このような現象は一般に「例外」と呼ばれます. 例外が発生した場合,プログラムでは適切な対応,少なくとも例外が発生したことを知ることはその対策のために重要です.
以下のExceptionTest.javaをコピーして動作を確認してみましょう.

ExceptionTest.java

1
2
3
4
5
6
7
8
9
10
11
12
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
    }
 
    static void test(int i, int j) {
        System.out.println(i / j);
    }
}
6行目
mainメソッドのStringクラスの配列のargsの0番目と1番目の要素を引数とするtestメソッドを呼んでいます.
9-11行目
与えられた引数iとjにたいしてi/jを計算して出力しています.

出力

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
	at jp.ac.utsunomiya_u.is.ExceptionTest.main(ExceptionTest.java:6)
ArrayIndexOutOfBoundsExceptionは負や規定サイズ以上の不正なインデックスで配列にアクセスした時にスローされる例外です.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        System.out.println(args.length);
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        System.out.println(i / j);
    }
}
例外処理の基本的な記述方法は以下の通りです.

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
    例外の発生する可能性のある処理
} catch (例外クラス1 変数名1){
    例外クラス1と関連のある例外が発生した時に実行する処理
} catch (例外クラス2 変数名2){
    例外クラス2と関連のある例外が発生した時に実行する処理
} catch (例外クラス3 変数名3){
    例外クラス3と関連のある例外が発生した時に実行する処理
}
...
finally {
    例外が発生した場合も発生しなかった場合でも実行される処理
}
WORK2の例では配列アクセスでArrayIndexOutOfBoundsExceptionが発生する可能性があるので,try節で例外が発生したら,そのEcceptionをcatch節で捕捉して,例外処理を書いています.

mainメソッドの引数の設定

アプリケーション実行時にパラメータを渡すためには,C++と同様にJavaでもコマンドライン引数を利用出来ます. Javaの場合,コマンドライン引数はmainメソッドのStringクラスの配列として渡されます.
今,args.length=0であるので,args[0]とargs[1]にアクセスすると,ArrayIndexOutOfBoundsExceptionが発生します.
eclipseでのコマンドライン引数の設定は以下のように行います.
1 "Run"->"Run Configurtations..."を選択します.
2 "Run Configrations"ウィンドウで"(x)=Arguments"タブを選択します
3 ”Program arguments:”で引数を記述します.
上記の手順に従って,コマンドライン引数を10 5に設定し,動作を確認してみましょう.
コマンドライン引数をa bに設定し,動作を確認してみましょう.
Exception in thread "main" java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
	at jp.ac.utsunomiya_u.is.ExceptionTest.main(ExceptionTest.java:8)
プログラムでは整数が入力されることが期待されていますが,実際には文字a bが与えられてしまっています. この時,NumberFormatExceptionの例外が発生しています.
今度は,このNumberFormatExceptionもcatch節で捕捉してみましょう.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        System.out.println(args.length);
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        System.out.println(i / j);
    }
}
コマンドライン引数を10 0に設定し,動作を確認してみましょう.
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at jp.ac.utsunomiya_u.is.ExceptionTest.test(ExceptionTest.java:15)
	at jp.ac.utsunomiya_u.is.ExceptionTest.main(ExceptionTest.java:8)
0で割ることによりArithmeticException(算術計算での例外)が発生します.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
        }
    }
}
次にfinallyの使い方を学びましょう. 例外が発生しても,しなくてもfinally節に書いた処理は必ず実行されます. だとしたら,finally節の役割とは何でしょう? finally節を置かなくても,try-catch節の後に,処理を書けば良いのではないでしょうか?
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            return;
        }
        System.out.println(i + j);
    }
}

出力

/ by zero
14-16行目のtry節で発生した例外が,16-19行目のcatch節でcatchされ処理されreturnされてしまいます. この場合,22行目の処理は行われません.
このように,catch節から遷移が発生するような構造の場合,困る場合があります. そこで,finally節を用います.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package jp.ac.utsunomiya_u.is;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            return;
        } finally {
            System.out.println(i + j);
        }
    }
}

出力

/ by zero
10
このように,catch節でreturnがあっても,必ずfinally節が処理されます. 当然,例外が発生しなくても処理が行われます.

チェック例外

Javaの例外は に分類できます. これまで出てきたRuntimeExceptionを継承するサブクラスは下記のような継承関係にあります. これまで見てきた例外は全てRuntimeExceptionを継承する非チェック例外なので,必ずしもtry-catch節で捕捉する必要はありません. では,チェック例外について学んでいきましょう.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package jp.ac.utsunomiya_u.is;
 
import java.io.FileInputStream;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
        test2();
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            return;
        } finally {
            System.out.println(i + j);
        }
    }
 
    static void test2() {
        FileInputStream fileInputStream = new FileInputStream("test.tex");
    }
}
このプログラムではFileInputStreamクラスのコンストラクタが例外FileNotFoundExceptionを投げます. 例外FileNotFoundExceptionはチェック例外なので,適切に処理しなければなりません. 処理しない場合,コンパイルエラーとなります.
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package jp.ac.utsunomiya_u.is;
 
import java.io.FileInputStream;
import java.io.FileNotFoundException;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        System.out.println(args.length);
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
        test2();
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            return;
        } finally {
            System.out.println(i + j);
        }
    }
 
    static void test2() {
        try {
            FileInputStream fileInputStream = new FileInputStream("test.txt");
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }
}

出力

test.txt (指定されたファイルが見つかりません。)
以下のようにExceptionTest.javaを修正して動作を確認してみましょう.

ExceptionTest.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package jp.ac.utsunomiya_u.is;
 
import java.io.FileInputStream;
import java.io.FileNotFoundException;
 
public class ExceptionTest {
 
    public static void main(String[] args) {
        System.out.println(args.length);
        try {
            test(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println(e.getMessage());
        } catch (NumberFormatException e) {
            System.out.println(e.getMessage());
        }
        try {
            test2();
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }
 
    static void test(int i, int j) {
        try {
            System.out.println(i / j);
        } catch (ArithmeticException e) {
            System.out.println(e.getMessage());
            return;
        } finally {
            System.out.println(i + j);
        }
    }
 
    static void test2() throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("test.txt");
    }
}
35行目
FileInputStreamクラスのコンストラクタが投げる例外FileNotFoundExceptionをすぐに処理せず,test2メソッドがスローして呼び出し元に処理を委託出来ます. これを「例外の委譲」と呼びます.
17, 19-21行目
test2メソッドでスローされた例外を受け取りtry-catch節で適切に処理しています.
更に,任意の場所で

1
throw new HogeException();
のように例外を呼び出すことも出来ます. 当然,Exceptionクラスやそのサブクラスを継承して自作の例外クラスを実装することも可能です.

try-with-resources 文

以下のTextCopy.javaをコピーして動作を確認してみましょう.

TextCopy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package jp.ac.utsunomiya_u.is;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class TextCopy {
 
    /**
     * コピー元ファイル名
     */
    private static final String INPUT_FILE_NAME = "in.txt";
    /**
     * コピー先ファイル名
     */
    private static final String OUTPUT_FILE_NAME = "out.txt";
 
    public static void main(String[] args) {
        FileReader fr = null;
        BufferedReader br = null;
        FileWriter fw = null;
        BufferedWriter bw = null;
        try {
            // FileReaderのインスタンス生成
            fr = new FileReader(INPUT_FILE_NAME);
            // BufferedReaderのインスタンス生成
            br = new BufferedReader(fr);
            // FileWriterのインスタンス生成
            fw = new FileWriter(OUTPUT_FILE_NAME);
            // BufferedWriterのインスタンス生成
            bw = new BufferedWriter(fw);
            String str;
            // brから一行読み込みstrに代入
            while ((str = br.readLine()) != null) {
                // strをそのままbwに書き込み
                bw.write(str);
                bw.newLine();
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(TextCopy.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(TextCopy.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            // 各Reader.Writerをnullチェックしてからclose
            try {
                if (br != null) {
                    br.close();
                }
                if (fr != null) {
                    fr.close();
                }
                if (bw != null) {
                    bw.close();
                }
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(TextCopy.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
Javaでも,ファイルやソケットのようなリソースは使用が終わったら閉じる必要があります. そのため,ReaderやWriterやInputStreamやOutputStreamのようなClosableインタフェースを実装しているクラスのサブクラスはそのリソースの使用後にcloseメソッドを明示的に呼ぶ必要があります. チェック例外をtry-catch節で捕捉した場合,finally節でcloseメソッドを呼びます. この時,nullチェックしてからcloseする必要があるので,コードが長くなりがちです.
そこで,Javaではtry-with-resources文が導入されています.
以下のようにTextCopy.javaを修正して動作を確認してみましょう.

TextCopy.javaの修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package jp.ac.utsunomiya_u.is;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class TextCopy {
 
    /**
     * コピー元ファイル名
     */
    private static final String INPUT_FILE_NAME = "in.txt";
    /**
     * コピー先ファイル名
     */
    private static final String OUTPUT_FILE_NAME = "out.txt";
 
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader(INPUT_FILE_NAME));
                BufferedWriter bw = new BufferedWriter(new FileWriter(OUTPUT_FILE_NAME))) {
            String str;
            // brから一行読み込みstrに代入
            while ((str = br.readLine()) != null) {
                // strをそのままbwに書き込み
                bw.write(str);
                bw.newLine();
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(TextCopy.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(TextCopy.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
try-with-resources文ではtryの直後に()でインスタンスを生成するように書きます. これによりfinally節でclose処理を明記しなくても自動的にclose処理をしてくれて,リソースの解放忘れを未然に防ぐ事が出来ます. try-with-resources文が利用できるのはClosableインタフェースまたは,AutoClosableインタフェースを実装したクラスです.