【Java入門】例外処理について（try-catch-finally、検査例外、非検査例外、throws、throw）

#目的

Java言語を含めたプログラミングの学習を始めたばかりの方、既学習者の方は復習用に、
今回は**例外処理**について学ぶために書いています。


[【Java入門目次】](https://qiita.com/morioheisei/items/6c7fc063c26e63d890ac)
・[変数と型](https://qiita.com/morioheisei/items/3d27e82d6cd88068dded)
・[型変換](https://qiita.com/morioheisei/items/28ab5fd69570e01cd317) 
・[変数のスコープ](https://qiita.com/morioheisei/items/c56ab32b68ce92d32206)
・[文字列の操作](https://qiita.com/morioheisei/items/e58adadd8535971b69be)
・[配列の操作](https://qiita.com/morioheisei/items/b6acbb1b0c07b40e7f56)
・[演算子](https://qiita.com/morioheisei/items/63088c61e8234dc1f7f5)
・[条件分岐](https://qiita.com/morioheisei/items/194cdc73a29bddade0a4)
・[繰り返し処理](https://qiita.com/morioheisei/items/e686e33b60c6a60e87f0)
・クラスについて（準備中）
・抽象クラス（準備中）
・インターフェース（準備中）
・カプセル化（準備中）
・モジュールについて（準備中）
・例外処理について ←今ここ
・[ラムダ式について](https://qiita.com/morioheisei/items/2bfcc3d8d8440b00e6a9)
・[Stream APIについて](https://qiita.com/morioheisei/items/3a7be38b4d3ef712ccb2)

#例外処理とは

ソースコードに文法の間違いなどがある場合、コンパイルの時点でエラーが出力され、実行出来ないため文法のミスや間違いを修正をする事が出来る。

しかし、コンパイルの時点では分からなかったが、実行してから気づくエラーを例外（Exception）と呼ぶ。

・実行環境のトラブルやプログラムからの対処不可能な事態を**エラー**
・プログラムから対処可能な事態を**例外**

上述の想定外の動作（例外）を防ぐため、対応する処理の事を**例外処理**という。

#例外のクラス

・Throwable←エラー、例外のスーパークラス（親クラス）

Throwableクラスのサブクラス（子クラス）には、
・**Errorクラス**
・**Exception** 

Exceptionクラスのサブクラス（子クラス）には、
・**RuntimeExceptionクラス**
・**その他のクラス**

RuntimeExceptionクラスにはさらにサブクラス（子クラス）がある。

#例外処理の方法

##①try-catch-finallyブロック

###try-catch
まずは、普通にコードを書いてみます。

```Java:Main.java
class Error {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    // fruits配列を一つずつ取り出すが、配列の要素外もアクセスしてみる
    for(int i = 0; i < 4; i++) {
      System.out.println(fruits[i]);
    }
    System.out.println("フルーツ全部を表示した");
  }
}
```

```Java:出力結果
りんご
バナナ
みかん
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
        at Main.main(Main.java:6)
```

上記の様に表示されました。
最後の「フルーツ全部を表示した」は出力されることなく、プログラムが途中で終了しています。


次にtry-catch文を用いて上記のコードを修正してみます。

`tryブロック`に、例外が発生しそうな箇所を記述します。
`catch( 例外クラス名 変数名 ）`を記述して、
`catchブロック`に、例外が発生した時の処理を記述します。

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    try {
      // 例外が発生しそうな処理
      for(int i = 0; i < 4; i++) {
        System.out.println(fruits[i]);
      }
    } catch(ArrayIndexOutOfBoundsException e) {
      // 例外が発生した時の処理
      System.out.println("例外が発生しました");
    }
    System.out.println("フルーツ全部を表示した");
  }
}
```

実行すると、

```Java:出力結果
りんご
バナナ
みかん
例外が発生しました
フルーツ全部を表示した
```
上記の様に出力されました。
4回目のループで例外が発生し、catchブロック内の処理が実行されています。
そして、プログラムが途中で終了することなく、最後の「フルーツ全部を表示した」まで出力されています。

###finally

`finallyブロック`には、例外が発生してもしなくても必ず実行したい処理を記述します。

例外が発生しない場合（配列の要素内にアクセス）、

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    try {
      // 例外が発生しそうな処理
      for(int i = 0; i < 3; i++) {
        System.out.println(fruits[i]);
      }
    } catch(ArrayIndexOutOfBoundsException e) {
      // 例外が発生した時の処理
      System.out.println("例外が発生しました");
    } finally {
      // 例外の有無に関わらず必ず実行される
      System.out.println("必ず処理する");
    }
    System.out.println("フルーツ全部を表示した");
  }
}
```
実行すると、

```Java:出力結果
りんご
バナナ
みかん
必ず処理する
フルーツ全部を表示した
```

上記の様にfinallyブロック内の処理が実行されています。

次に例外が発生する場合（配列の要素外にアクセス）、

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    try {
      // 例外が発生しそうな処理
      for(int i = 0; i < 4; i++) {
        System.out.println(fruits[i]);
      }
    } catch(ArrayIndexOutOfBoundsException e) {
      // 例外が発生した時の処理
      System.out.println("例外が発生しました");
    } finally {
      // 例外の有無に関わらず必ず実行される
      System.out.println("必ず処理する");
    }
    System.out.println("フルーツ全部を表示した");
  }
}
```

実行すると、

```Java:出力結果
りんご
バナナ
みかん
例外が発生しました
必ず処理する
フルーツ全部を表示した
```

上記の様に出力されました。
例外が発生して、catchブロック内の処理が実行されます。
そして、finallyブロック内の処理が実行され、最後の「フルーツ全部を表示した」まで出力しています。

###try-catch-finallyの注意点

書き方の組み合わせとして、上記の通り
・try-catch
・try-finally
・try-catch-finally
はOK！


以下の書き方はコンパイルエラーとなります。

・tryブロックのみ

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    // tryブロックのみはコンパイルエラー
    try {
      for(int i = 0; i < 4; i++) {
        System.out.println(fruits[i]);
      }
    }
    System.out.println("フルーツ全部を表示した");
  }
}
```

・try-finally-catch、catch-finally-try

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    // try-finally-catchはコンパイルエラー
    try {
      for(int i = 0; i < 4; i++) {
        System.out.println(fruits[i]);
      }
    } finally {
      System.out.println("必ず処理する");
    } catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("例外が発生しました");
    }

    // catch-finally-tryもコンパイルエラー
    catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("例外が発生しました");
    } try {
      for(int i = 0; i < 4; i++) {
        System.out.println(fruits[i]);
      }
    } finally {
      System.out.println("必ず処理する");
    }
  }
}
```

###例外の種類

例外処理の方法としてtry-catch-finallyブロックによる例外処理を学びましたが、
ここで例外の種類について触れ、実際に使用するタイミングを学びましょう。

**非検査例外**
例外処理を記述したかどうかをコンパイラが検査しない例外のこと（例外処理の記述は任意）
RuntimeExceptionクラス以下が対象となる。

例外の処理は任意なので、javaの文法自体に問題はなくコンパイルは出来てしまいます。

```Java:Main.java
class Main {
  public static void main(String[] args) {
    int[] num = {10, 20, 30};
    System.out.println(num[3]); // num配列の範囲外を出力しようとしている
  }
}
```

上記ファイルをコンパイルし、実行すると

```出力結果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
        at Main.main(Main9.java:71)
```

RuntimeExceptionのサブクラスである、ArrayIndexOutOfBoundsExceptionの例外が発生しています。
配列のインデックス（添え字）が範囲外になった例外です。

このような例外はプログラマーがコーディング時に確認をすることで防げるため、通常は例外処理を行わないです。
（一々例外が発生しそうな箇所全てに例外処理を書いていると煩雑なコードになる）

でも一応、try-catch文で囲ってみます。

```Java:Main.java
class Main {
  public static void main(String[] args) {
    String[] fruits = {"りんご", "バナナ", "みかん"};

    int[] num = {10, 20, 30};
    try {
      System.out.println(num[3]); // num配列の範囲外を出力しようとしている
    } catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("例外が発生しました");
    }
  }
}
```

コンパイルして実行すると、

```出力結果
例外が発生しました
```

catch文内の処理が実行されています。


**検査例外**
例外処理を記述したかどうかをコンパイラがチェックする例外のこと（例外処理の記述が必須）
RuntimeException以外のその他のクラスが対象となる。

例外処理の記述は必須となり、記述がないとコンパイル時にエラーになります。
試しに見てみましょう。

例外処理の記述がない場合。

```Java:Main.java
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

class Main {
  public static void main(String[] args) {
    // ファイルのパスを指定する
    File file = new File("test.text");

    // ファイルの中身を1行を取得する
    FileReader fileReader = new FileReader(file);
    BufferedReader bufferedReader = new BufferedReader(fileReader);
    String str = bufferedReader.readLine();

    // ループを回して１行ずつ表示していく
    while(str != null){
      System.out.println(str);
      str = bufferedReader.readLine();
    }

    // 最後にファイルを閉じてリソースを開放する
    bufferedReader.close();
  }
}
```

これをコンパイルすると、

```出力結果
    Main.java:13: エラー: 例外FileNotFoundExceptionは報告されません。スローするには、捕捉または宣言する必要があります
    FileReader fileReader = new FileReader(file);
                                ^
    Main.java:15: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        String str = bufferedReader.readLine();
                                            ^
    Main.java:20: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
          str = bufferedReader.readLine();
                                      ^
    Main.java:24: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        bufferedReader.close();
                            ^
    エラー4個
```

例外処理の記述をせずにコンパイルするとコンパイルエラーになってしまいます。

次にtry-catch文で囲ってみます。

```Java:Main.java
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

class Main {
  public static void main(String[] args) {
    
    // try-catchの例外処理を記述した場合
    try {
      // ファイルのパスを指定する
      File file = new File("test.text");

      // ファイルの中身を1行を取得する
      FileReader fileReader = new FileReader(file);
      BufferedReader bufferedReader = new BufferedReader(fileReader);
      String str = bufferedReader.readLine();

      // ループを回して１行ずつ表示していく
      while(str != null){
        System.out.println(str);
        str = bufferedReader.readLine();
      }

      // 最後にファイルを閉じてリソースを開放する
      bufferedReader.close();

    } catch (IOException e) {
      System.out.println("例外が発生しました");
    }
  }
}
```

コンパイルするとコンパイルエラーが出ません。
実行すると、

```出力結果
あいうえお
かきくけこ
さしすせそ
```

とtest.txtファイルの中身が出力されました。

今度はファイルのパス指定部分をわざと間違えてみます。

```Java:Main.java
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

class Main {
  public static void main(String[] args) {
    
    // try-catchの例外処理を記述した場合
    try {
      // ファイルのパスを指定する
      File file = new File("aaaaaaaaa.text"); // 存在しないテキストファイルを指定する

      // ファイルの中身を1行を取得する
      FileReader fileReader = new FileReader(file);
      BufferedReader bufferedReader = new BufferedReader(fileReader);
      String str = bufferedReader.readLine();

      // ループを回して１行ずつ表示していく
      while(str != null){
        System.out.println(str);
        str = bufferedReader.readLine();
      }

      // 最後にファイルを閉じてリソースを開放する
      bufferedReader.close();

    } catch (IOException e) {
      System.out.println("例外が発生しました");
    }
  }
}
```

コンパイルは出来ます。実行してみると、

```出力結果
例外が発生しました
```

と出力されました。
例外が発生し、catch文内の処理が実行されています。

上記の様にRuntimeException以外のその他のクラスの場合、例外処理を記述していなければコンパイルエラーとなってしまうケースもあるので意識しなければいけませんね。

##②throws

try-catch-blockブロックの例外処理以外に、throwsキーワードによる例外処理が出来ます。

このキーワードは、例外が発生する可能性があるメソッドを定義する時に、
`throws 発生する例外クラス名`と指定することで、
このメソッドの呼び出し元に例外が転送される仕組みとなっています。

具体的に上述のサンプルコードを元にメソッド化して試してみます。

ファイルを読み込んで出力する動作を別クラスでメソッドとして定義します。
このメソッドを定義する時、`throwsキーワード`を用います。

```Java:GetText.java
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

class GetText {
  // throws で指定されたメソッドは呼び出し元へ例外を転送する
  public void getText() throws IOException {
    // ファイルのパスを指定する
    File file = new File("aaaaaaaaa.text"); // 存在しないテキストファイルを指定する

    // ファイルの中身を1行を取得する
    FileReader fileReader = new FileReader(file);
    BufferedReader bufferedReader = new BufferedReader(fileReader);
    String str = bufferedReader.readLine();

    // ループを回して１行ずつ表示していく
    while(str != null){
      System.out.println(str);
      str = bufferedReader.readLine();
    }

    // 最後にファイルを閉じてリソースを開放する
    bufferedReader.close();
  }
}
```

Main.javaにてgetTextメソッドを呼び出す時、もし例外が発生した場合はこちらで記述したcatch文の処理が実行されます。

```java:Main.java
import java.io.IOException;

class Main {
  public static void main(String[] args) {
    // throwsの時
    try {
      GetText gt = new GetText();

      // throwsキーワードを用いた、getTextメソッドを呼ぶ
      gt.getText();

    // 例外をキャッチする
    } catch (IOException e) {
      System.out.println("例外が発生しました");
    }
  }
}
```

このファイルをコンパイルすると、コンパイルは出来ます。ただ実行してみると、

```出力結果
例外が発生しました
```

getText内にて存在しないファイルにアクセスするという例外が発生しました。
ただ、処理をしているメソッド内にはtry-catch文の記述はありません。




メソッドの定義時に`throws 発生する例外クラス名`と宣言した事により、
このメソッドの呼び出し元にあるcatch文に例外がスローされ（投げられ）、例外処理が実行されます。



ちなみに、メソッドの呼び出し元でtry-catch文を無くしてみて、

```java:Main.java
import java.io.IOException;

class Main {
  public static void main(String[] args) {
    // throwsの時で、try-catch文を消した時
    GetText gt = new GetText();
    gt.getText();
  }
}
```

コンパイルすると...

```出力結果
Main.java:16: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
    gt.getText();
              ^
エラー1個
```

コンパイルエラーになってしまいます。
throwsキーワードを用いたメソッドを定義した時も、メソッドの呼び出し元ではtry-catch文を用いた例外処理の記述がないとコンパイルエラーになってしまいますので、ご注意を。


##③throw

`throwキーワード`を用いて、プログラム内で明示的に例外をスローすることが出来ます。
throwsと違う点は、throwsは例外が発生した時に補足しますが、throwは明示的に例外を補足します。
ですので、自分で意図的に例外を発生させる事が出来ます。

```java:Main.java
class Main {
  public static void main(String[] args) {

    int[] num = {10, 20, 30};
    try {
      for(int i = 0; i < num.length; i++) {
        System.out.println(num[i]);
        if(num[i] < 20) {

          // 例外を明示的に投げています
          throw new Exception();
        }
      }
    } catch (Exception e) {
      System.out.println("例外が発生しました");
    }
  }
}
```

上記のコードでコンパイル、実行すると、

```出力結果
10
例外が発生しました
```

num配列を一つずつ出力する中で、20を超えたら例外を発生させるよう明示的にしました。
結果的に2つめの20を出力しようとするときに、catch分内の処理を実行しています。

throwキーワードは任意の場所で例外をスローする事ができるのが、throwsキーワードとの違いです。

#終わりに

Java文法が間違えていなければコンパイル出来てしまい、
プログラムの実行途中に処理が止まってしまうことを防ぐために備えての例外処理について学びました。

try-catchでの例外処理の記述が必須な検査例外というのがあるので気をつけたいですね。

まだまだ表面的な知識のみなので、もっと深掘りして実践で使えるよう理解していきたいですね。