Javaの例外処理について

##はじめに

この記事はこれからJavaを勉強する方に向けた私自身の理解している内容の記録です。

Javaの機能や記載方法についてを、下記のリストに分けて記載していきます。今回は例外処理です。

・[変数と型、型変換](https://qiita.com/pitan109/items/2538b234a2c6e20c037f)
・[変数のスコープ](https://qiita.com/pitan109/items/b30589bc4c2e8169734c)
・文字列の操作（準備中）
・配列の操作（準備中）
・演算子（準備中）
・条件分岐（準備中）
・繰り返し処理（準備中）
・例外処理 現在ページ
・クラスについて（準備中）
・抽象クラス（準備中）
・インターフェース（準備中）
・カプセル化（準備中）
・モジュールについて（準備中）

##例外処理とは
例外処理の考え方としては、コードの中で想定外な動作が行われ、例外が発生した時のためにあらかじめ対応を記載しておくことです。

##例外はそもそも何か エラーとの違い
例外(Exception)というのは、プログラム実行中の異常な出来事のことです。よくエラー(Error)と混合されてしまいますが、全く違うもので、エラーは仮想マシン上でも対応できない異常な出来事で、強制終了してしまうので例外処理では対応できません。
例外では動作を終わらせることなく、プログラムを進められます。

##例外処理の仕組みについて
try~catch文というのを解説していきます。

```java
class Main{
    public static void main(String args[]){
        try{
            int[] array = {0,1,2};

            System.out.println(array[3]);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("例外が出ました");
        }
    }
}
```

上記の記載がtry~catchの形となります。仕組みとしては、tryとcatchの中でcatchのクラスと同じ例外が出た時に例外文をcatch(例外文)に渡し、catchの中身が動作する。
概ねこんな感じだと思っていただければわかりやすいです。

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

```
例外が出ました
```

が表示されます。catchの中身が動いていますね。

##チェック例外(検査例外)
チェック例外というのは、例外処理を入れてくれないと動いてくれない処理に使用する例外処理です。
この言葉だけだとイメージがしにくいと思いますので、実際に記載してみます。

下記コードは例外処理がありません。

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

class Main{
    public static void main(String args[]){
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;

        // 入力ファイル
        fileReaderr = new FileReader("test.txt");
        bufferReader = new BufferedReader(fileReaderr);

        // 1行づつ読み込んで出力
        String line;
        while ((line = bufferReader.readLine()) != null) {

            System.out.println(line);

            //意図的にストリームを閉じ、IOExceptionを発生させる
            bufferReader.close();
        }
    }
}
```

これをコンパイルしようとすると

```
test.java:11: エラー: 例外FileNotFoundExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        fileReaderr = new FileReader("test.txt");
                      ^
test.java:16: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        while ((line = bufferReader.readLine()) != null) {
                                            ^
test.java:21: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
            bufferReader.close();
                              ^
```

少し見にくいかもしれませんが、上記の記載は例外処理がないとこの動作は動かさせないよといっているようなニュアンスです。

IOExceptionは例外処理を入れないとコンパイルを許してくれません。なのでIOExceptionはチェック例外の対象なのです。

なのでこれらの動作をしている箇所をtry~catchで囲ってみましょう。

```java

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

class Main{
    public static void main(String args[]){
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;

        try {
            // 入力ファイル
            fileReaderr = new FileReader("test.txt");
            bufferReader = new BufferedReader(fileReaderr);

            // 1行づつ読み込んで出力
            String line;
            while ((line = bufferReader.readLine()) != null) {

                System.out.println(line);

                //意図的にストリームを閉じ、IOExceptionを発生させる
                bufferReader.close();
            }
        } catch (IOException e) {
            System.out.println("IOExceptionが発生");
        }
    }
}

```

こちらをコンパイルして実行すると

```
IOExceptionが発生
```

処理の中ではtxtファイルを読み込んでwhileの中で強制終了することで、tryの中でIOExceptionが発生しますね。
そのときに例外処理が働き、catchの中の出力動作がされています。

また、throwsをメソッドに定義することで、コンパイルエラーを起きなくすることもできます。

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

class Main{
    public static void main(String args[]) throws IOException{
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;

        // 入力ファイル
        fileReaderr = new FileReader("test.txt");
        bufferReader = new BufferedReader(fileReaderr);

        // 1行づつ読み込んで出力
        String line;
        while ((line = bufferReader.readLine()) != null) {

            System.out.println(line);

            //意図的にストリームを閉じ、IOExceptionを発生させる
            bufferReader.close();
        }
    }
}
```

この記載でもコンパイルはできます。この記載でもチェック例外のフォローはできているということですね。ただ実行すると

```
Exception in thread "main" java.io.FileNotFoundException: test.txt (No such file or directory)
	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 Main.main(test.java:11)
```

実行中に例外になり処理が終了します。try~catchと違うのは例外が出た時のフォロー(catch)がないことです。

処理の流れとしてはtry~catchの方は先にIOExceptionが発行され、その時点でcatch文の処理に飛んでいるため、別の例外は起こらない動作となりました。

throwsはこの時点ではIOExceptionが許容されているという状態だけですので、その後別の例外が起きてプログラムが終了しているということになります。
throwsについては別項目に詳しく記載しているので、そちらも見ていただければと思います。

ちなみに、チェック例外という概念があるのがJavaだけというのを聞いたことがあります。なので他言語から来た方は処理は間違っていないのにエラーが出てくることに困惑することがあるかもしれません。

## 非チェック例外
こちらは他言語でもよくある形で、例外処理を記載するのは任意のExceptionのことです。今回はArrayIndexOutOfBoundsException(配列で存在しない要素にアクセスする)で確認していこうと思います。

こちらは例外処理がない記載です。

```java
class Main{
    public static void main(String args[]){
        int[] array = {0,1,2};

        System.out.println(array[3]);
    }
}
```

出力で配列arrayの3番目を参照しようとしていますが、0~2までしか配列はありません。
でもコンパイルエラーにはならず、実行すると

```
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at Main.main(test.java:6)
```

ArrayIndexOutOfBoundsExceptionが出て来ました。配列の範囲外を見ようとしていると言われました。

今度は例外処理を入れて動作させて見ます。見出しの例外処理の仕組みで記載しているコードです。

コンパイルは通り、実行すると同じように

```
例外が出ました
```

catch文の中のメッセージが表示できました。

非チェック例外というのは基本は避けれるエラーで、記載する義務はありません。どちらかというと念のためといったニュアンスな気がします。

## finally
catchの後にfinallyを置くケースも紹介します。

```java
class Main{
    public static void main(String args[]){
        try{
            int[] array = {0,1,2};

            System.out.println(array[2]);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("例外が出ました");
        }finally{
            System.out.println("どちらでも動く");
        }
    }
}
```

こちらは例外は出ない処理ですが、コンパイルして実行すると

```
2
どちらでも動く
```

といった感じでfinally内の処理も動いています。今度は例外が出る記載で試して見ます。

```java
class Main{
    public static void main(String args[]){
        try{
            int[] array = {0,1,2};

            System.out.println(array[3]);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("例外が出ました");
        }finally{
            System.out.println("どちらでも動く");
        }
    }
}
```

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

```
例外が出ました
どちらでも動く
```

このような表示になりました。
このようにtry内で例外の有無にかかわらず表示させています。どちらになっても動かして起きたい処理はこちらに記載する形になります。

##throw
これまでは例外発生時にこの動作をするという流れで例外処理を追っていきましたが、throw文により自分で例外処理を動作させる(用語としては例外をスローする)ことができます。

まずはthrowを記載してみましょう。

```java
class Main{
    public static void main(String args[]){
        System.out.println("例外スロー");
        try{
            throw new Exception("例外を投げる");
        }catch(Exception e){
            System.out.println("例外処理");
        }
    }
}
```

実行すると

```
例外スロー
例外処理
```

この処理はtryの中で

```java
throw new IOException("例外を投げる");
```

が動作しています。この記載でExceptionが発行され、それをcatch文が補足し、2つの出力を行なっている流れになります。

## throws
throwsは例外発生時に例外処理をメソッド呼び出し元で行う指定です。
この言葉だけではイメージがしにくいと思うので、こちらも実際に記載します。

```java
class Main{
    public static void main(String args[]){
        System.out.println("例外スロー");
        try{
            Main.mikan();
        }catch(Exception e){
            System.out.println("例外処理");
        }
    }

    static void mikan() throws Exception{
        throw new Exception("例外を投げる");
    }
}
```

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

```
例外スロー
例外処理
```

と表示されます。動作の流れとしてはmainメソッドのtry文内でmikanメソッドを呼び出しています。

呼び出した先のmikanメソッドでthrows Exceptionを定義しています。意味としてシンプルにいうと、Exceptionがメソッド内で発行されたら、呼び出し元に渡すという意味です。

メソッド内ではExceptionを発行しているため、呼び出し元に例外を投げます。それをcatchが補足し、例外処理の出力が動くというのが一連の流れです。

ただ、このサンプルではイメージのしやすさのため、Exception処理をcatchで補足しているようにしていますが、実際に記載するときはこのメソッドで起きる可能性のある例外を記載するのがいいと思います。なのでここからのサンプルはIOException(チェック例外)を受け取るように記載します。


throwsで注意するべきは、チェック例外を投げるようメソッドに定義しているのであれば、呼び出し元でtry~catchを囲わないとエラーになるということと、throwsを定義していなければ、try~catchで呼び出し元を囲っていてもエラーになることです。こちらも実際に記載します。

こちらはメソッド呼び出し元はtry~catchで囲っていますが、メソッド側はthrowsを定義していない状態です。

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

class Main{
    public static void main(String args[]){
        try{
            Main.mikan();
        }catch(IOException e){
            System.out.println("IOExceptionが出ています");
        }
    }

    static void mikan(){
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;
        // 入力ファイル
        fileReaderr = new FileReader("test.txt");
        bufferReader = new BufferedReader(fileReaderr);

        // 1行づつ読み込んで出力
        String line;
        while ((line = bufferReader.readLine()) != null) {
            System.out.println(line);

            //意図的にストリームを閉じ、IOExceptionを発生させる
            bufferReader.close();
        }
    }
}
```

こちらをコンパイルしようとすると


```
test.java:9: エラー: 例外IOExceptionは対応するtry文の本体ではスローされません
        }catch(IOException e){
         ^
test.java:18: エラー: 例外FileNotFoundExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        fileReaderr = new FileReader("test.txt");
                      ^
test.java:23: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        while ((line = bufferReader.readLine()) != null) {
                                            ^
test.java:27: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
            bufferReader.close();
                              ^
エラー4個
```

エラーが表示されます。下3つのエラーはtry~catchで囲っていない時のエラーと似ている内容で、チェック例外なのにIOExceptionの対応が記載されていないように見えている状態のためのエラーです。

上のエラーはtry~catchの中でIOExceptionは起きないと言っているエラーです。実際はメソッドの中で起きるのですが、それを宣言していないため、try側が確認できないということですね。

次はメソッド呼び出し元にtry~catchの記載がなく、メソッドの方ではthrowsを定義している方で動作を確認しましょう。

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

class Main{
    public static void main(String args[]){
        Main.mikan();
    }

    static void mikan() throws IOException{
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;
        // 入力ファイル
        fileReaderr = new FileReader("test.txt");
        bufferReader = new BufferedReader(fileReaderr);

        // 1行づつ読み込んで出力
        String line;
        while ((line = bufferReader.readLine()) != null) {
            System.out.println(line);

            //意図的にストリームを閉じ、IOExceptionを発生させる
            bufferReader.close();
        }
    }
}
```

このコードはチェック例外のサンプルでtryで囲わないといけない処理をmikanメソッドに写した記載です。こちらをコンパイルしようとすると

```
例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
        Main.mikan();
                  ^
```

とエラーがでます。こちらは先ほどとは逆で、呼び出し元側でIOExceptionが起きることがthrows定義によってわかっている状態ですが、チェック例外なのでtry~catchで囲ってあげないといけないというエラーです。

上記サンプルをtry文で囲います。

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

class Main{
    public static void main(String args[]){
        try{
            Main.mikan();
        }catch(IOException e){
            System.out.println("IOExceptionが出ています");
        }
    }

    static void mikan() throws IOException{
        BufferedReader bufferReader = null;
        FileReader fileReaderr = null;
        // 入力ファイル
        fileReaderr = new FileReader("test.txt");
        bufferReader = new BufferedReader(fileReaderr);

        // 1行づつ読み込んで出力
        String line;
        while ((line = bufferReader.readLine()) != null) {
            System.out.println(line);

            //意図的にストリームを閉じ、IOExceptionを発生させる
            bufferReader.close();
        }
    }
}
```

これでコンパイルして実行すると

```
IOExceptionが出ています
```

と表示されます。mikanメソッドで例外が発生し、呼び出し元でcatchしてcatch文の中の出力処理ができています。
throwsで定義されているチェック例外があるときには、呼び出し元の記載に注意しましょう。


##終わりに
例外についてを色々記載していきました。上記でも少し触れましたが、Javaの勉強中であれば、チェック例外は確認しておくとエラーにも対応できると思います。