コンテンツにスキップ

例外処理

プログラムが実行される際、データ型が違うだの、メモリが足りないだの、用意した配列サイズ以上のデータだの、ファイルがみつからないだの…と様々なエラーが出る。これを処理するのが例外処理

すなわち、色々エラー起きたけど、このあとどうするよ?っていう処理のこと。

例外処理には大きく二つある。

2つの例外

検査例外の代表として挙げられるのが、IOExceptionBufferedReaderとか使うとき、throws IOExceptionとかしないとコンパイルエラーする.

非検査例外の代表として挙げられるのが、ArrayIndexOutOfBoundsExceptionNullPointerException。配列のサイズ以上の入力とか、実行時にエラーがでる.

非検査例外 - 非チェック例外とも。コンパイラが例外処理を埋め込んでいるかという検査をしない。

非検査例外は、例外処理をわざわざ実装する必要がありません。(コンパイル通るし)
後ほど説明するtry-catchなど利用してエラー発生時の処理を書くことはもちろんできます。

検査例外 - チェック例外とも。コンパイラが例外処理を埋め込んでいるか検査する。

例外処理を書かないと、ダメです。

例外処理の書き方

throwsを用いた書き方

例外が発生しました!さあどうしましょう。というときに、親となるクラスや呼出しもとにぶん投げます。

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

public class IOeTest{
    public static void main(String[] args) throws IOException {
        String fname = args[0]; // 1.
        FileReader freader = new FileReader(fname); // 2.
        BufferedReader buf = new BufferedReader(freader); // 3.
        while(buf.ready()){
            System.out.println(buf.readLine()); //4.
        }
    }
}

上記は、

  1. args[0] からファイル名を読み
  2. FileReaderでファイルを準備し、
  3. BufferedReaderでバッファにぶち込み、
  4. buf.readLine()で一行ずつ出力する

プログラムです。

当然

  1. ファイル名を指定しなければ、引数0なんてないよ、と1の場所でエラーが出る。
  2. 存在しないファイル名を指定すると、そんなファイルないよ、と2の場所でエラーが出る。

です。

1は非検査例外です。1を吐かせてみると...

Exception in thread "main" Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    at IOeTest.main(IOeTest.java:7)
    at IOeTest.main(IOeTest.java:7)

java.lang.ArrayIndexOutOfBoundsExceptionがでます。

2は検査例外です。吐かせてみると...

Exception in thread "main" Exception in thread "main" java.io.FileNotFoundException: 絶対ないプログラムやで.java (そのようなファイルやディレクトリはありません)
java.io.FileNotFoundException: 絶対ないプログラムやで.java (そのようなファイルやディレクトリはありません)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:212)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:154)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:109)
    at java.base/java.io.FileReader.<init>(FileReader.java:60)
    at IOeTest.main(IOeTest.java:7)

エラーとして、java.io.FileNotFoundExceptionがでてきます。

Try-Catch-Finally

上記では、エラーをIOExceptionクラスなどに投げることを明示していました。

try-catch-finallyを利用すると、自身で実装できます。

上記例を基に、やってみましょう。

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

public class IOeTryTest{
    public static void main(String[] args) {
        String fname = args[0];
        FileReader freader;
        BufferedReader buf;
        try{
            System.out.println("TRY");
            freader = new FileReader(fname);
            buf = new BufferedReader(freader);
            while(buf.ready()){
                System.out.println(buf.readLine());
            }
        }catch(Exception e){
            // Exception(例外処理)クラスのeを持つ。
            // Standard Output
            System.out.println("CATCH");
            // Standard Error Output
            System.err.println("error occured.");
            System.err.println(e);
        }finally{
            System.out.println("FINALLY");
        }
        System.out.println("FINALLYあと");
    }
}

上記プログラムでは、tryブロックに続いてcatchブロック、finallyブロックがあります。また、メソッドにthrows IOExceptionが実装されていないです。

ここで各ブロックを概説しましょう。

BLOCK Description
try エラーの発生が予期される処理
catch エラー発生時の例外処理
finally エラーに関わらず終了前にする処理

ここで、エラー発生時と正常な動作を実行して、前のプログラムと比べて見てください。

オーバーロード

次章のメソッドプログラミングでも説明しますが、オーバーロードと呼ばれる概念があります。引数を変え、それ以外の名前を統一することで、引数データ型や数によって処理の振り分けを行うことです。
fname = args[0];tryブロック内に移動し、catchブロックを下記のように振り分けできる状態をつくれば、エラーの種別によって処理が振り分けされます。

        }catch(ArrayIndexOutOfBoundsException e){
            // Standard Output
            System.out.println("ArrayIndexOutOfBoundException catched");
        }catch(IOException e){
            System.out.println("IOException catched");
        }
        System.out.println("FINALLYあと");

try時のcatch/finally

  • try時に、0個以上のcatchまたはfinallyが必須です。
  • つまり、最低1個のcatchfinallyが必要になります。

throw

catch内で例外処理を行っている際、上位のIOExceptionクラスなどにエラーを投げて終了させたい場合があるとします。
その場合に用いられるのが、throwです。
上位に投げるため、メソッドにthrows IOExceptonなどを付加する必要があります。(catchの引数がIOException eなどではなく、単なるException eの場合、Exception`は付記せずとも予期されているので付記する必要はありません)

下記はthrowをテストするために上記のIOTryTest.javaをコピーして編集したIOTryTest2.javaとの差分です。差分とは、読んで字のごとく、差だけ算出することです。
差分には、Linuxのdiffコマンドに-upNオプションをつけて確認しています。

--- IOeTryTest.java 2020-06-26 14:29:55.232975007 +0900
+++ IOeTryTest2.java    2020-06-26 14:32:06.820360485 +0900
@@ -3,8 +3,8 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.io.FileNotFoundException;

-public class IOeTryTest{
-    public static void main(String[] args) {
+public class IOeTryTest2{
+    public static void main(String[] args) throws IOException {
         String fname = args[0];
         FileReader freader;
         BufferedReader buf;
@@ -15,13 +15,13 @@ public class IOeTryTest{
             while(buf.ready()){
                 System.out.println(buf.readLine());
             }
-        }catch(Exception e){
+        }catch(IOException e){
             // Exception(例外処理)クラスのeを持つ。
             // Standard Output
             System.out.println("CATCH");
             // Standard Error Output
             System.err.println("error occured.");
-            System.err.println(e);
+            throw e;
         }finally{
             System.out.println("FINALLY");
         }

上記では、3行目以降の差分と15行目以降の差分を確認できます。
-が消えた行、+が追加された行です。

上記のように、throwを用いることでもできます。


最終更新日: 2021年4月30日