例外処理の実装方法（Java）

今まで例外処理として、try-catch、throws等について、理解されていないままプロジェクトに参画されている方を結構見てきました。
これからプロジェクトへ参画される方や、実は自分もよくわからないまま実装しちゃってる・・・って方向けに例外処理の実装方法を簡単に説明します。

# 目次
[1.例外クラス](#1-例外クラス)
[2.検査例外(checked例外)](#2-検査例外checked例外)
[3.非検査例外(unchecked例外)](#3-非検査例外unchecked例外)
[4.例外処理](#4-例外処理)
[5.まとめ](#5-まとめ)

# 1. 例外クラス
プログラム実行中に例外が発生すると、本来想定しているプログラムがそこで一旦終了され、実装した例外処理が実行されます。

Javaの、[例外クラス(Throwable)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Throwable.html)は、大きく分けて、以下二つの例外に分類されます。
* 検査例外(checked例外)
* 非検査例外(unchecked例外)

それぞれの例外については後述で説明しますが、対象となるクラスは以下となります。

### 検査例外
* [RuntimeException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/RuntimeException.html)を除く、[Exception](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Exception.html)のサブクラス

### 非検査例外
* [RuntimeException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/RuntimeException.html)のサブクラス
* [Error](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Error.html)のサブクラス


図で表すと以下のイメージです。
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/ba3d2f56-8dcc-80b7-11e9-966320c2dfcf.png)|
|:--|

# 2. 検査例外(checked例外)
コンパイラが例外処理を実装しているか検査する例外。
例外処理の実装が必須。（例外処理を実装しないとコンパイルエラーになる）

### 検査例外(checked例外)の対象クラスの例
* [DataFormatException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/zip/DataFormatException.html)
* [IOException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/IOException.html)
* [ParseException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/text/ParseException.html)
* [SQLException](https://docs.oracle.com/javase/jp/11/docs/api/java.sql/java/sql/SQLException.html)

# 3. 非検査例外(unchecked例外)
コンパイラが例外処理を実装しているか検査しない例外。
例外処理の実装が任意。（例外処理を実装していなくてもコンパイルエラーにならない）

### 非検査例外(unchecked例外)の対象クラスの例

#### Errorのサブクラス例
* [OutOfMemoryError](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/OutOfMemoryError.html)
* [StackOverflowError](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/StackOverflowError.html)
* [NoClassDefFoundError](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/NoClassDefFoundError.html)

#### RuntimeExceptionのサブクラス例
* [ArithmeticException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ArithmeticException.html)
* [ClassCastException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassCastException.html)
* [DateTimeException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/time/DateTimeException.html)
* [ArrayIndexOutOfBoundsException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ArrayIndexOutOfBoundsException.html)
* [NullPointerException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/NullPointerException.html)
* [NumberFormatException](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/NumberFormatException.html)

# 4. 例外処理
検査例外をthrowするメソッドを利用する場合、例外処理を実装しないと、コンパイルエラーになってしまいます。
尚、例外のthrowとは、例外が投げられる（throw:スロー）ことを指します。
javaのthrowは、その処理の呼び出し元に対して例外を投げます。

以降、**FileReader**クラスを例に説明していきます。

## throwされる可能性がある例外クラス
FileReaderの[コンストラクタ](https://docs.oracle.com/javase/jp/8/docs/api/java/io/FileReader.html#FileReader-java.io.File-)を利用すると、**FileNotFoundException**が**throw**される可能性があります。
<font color="Red">**throws句**</font>の後続に記載されている、**FileNotFoundException**が、throwされる可能性がある例外クラスです。
```java
public FileReader(String fileName)
           throws FileNotFoundException
```
このように、throws句で定義された例外クラスを、利用する側で例外処理をしてあげる必要があります。

## 例外処理の実装方法
例外処理を行うには、以下の２択となります。
* try-catch句で処理する
* try-catch句を使わずにthrowsに定義する

## try-catch句で処理する
```java
public void hoge() {
    try {
        FileReader fileReader = new FileReader("/hoge.txt");
        System.out.println("Success");
    } catch (FileNotFoundException ex) {
        ex.printStackTrace();
        System.out.println("Failed");
    }
    System.out.println("Finish!!");
}
```

以下の箇所で、`FileNotFoundException`がthrowされる可能性があるので、try-catchで囲ってあげます。
```java
FileReader fileReader = new FileReader("/hoge.txt");
```

#### try句
例外が発生する可能性がある箇所を、tryブロックで囲います。
```java
try {
	FileReader fileReader = new FileReader("/hoge.txt");
	System.out.println("Success");
}
```
`System.out.println("Success")`で、**FileNotFoundException**は発生しませんが、`new FileReader("/hoge.txt")`で、**FileNotFoundException**が発生しなかった場合のみ、実行させるために一緒にtryブロックにいれてあげます。

#### catch句
tryブロックの中で、発生した例外をcatch（キャッチ）した際の処理を記述します。
```java
catch (FileNotFoundException ex) {
	ex.printStackTrace();
	System.out.println("Failed");
}
```
tryブロックの中で、**FileNotFoundException**が発生したら、ここへすぐさま飛んできて、ブロック内の処理を実行します。
実際の運用では、ログの出力や、DBのcommitやrollbackをさせたりします。

この例ではhogeメソッドの呼び出し元からは、以下が実行され、例外が発生した事実はわかりません。
* コンソールにStackTrace(例外の情報)を出力
* コンソールに、`Failed`を出力

なお、例外が発生しなかった場合、catchブロック内の処理は実行されません。

流れを表すとこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/0ad84ae0-ad59-6415-4bba-41b26e6ebd68.png)

もし、呼び出し元に、例外が発生したことを通知するために、対象の例外を同様にthrowする場合は以下のようになります。

```java
public void hoge() throws FileNotFoundException { // throws句を追加
    try {
        FileReader fileReader = new FileReader("/hoge.txt");
        System.out.println("Success");
    } catch (FileNotFoundException ex) {
        ex.printStackTrace();
        System.out.println("Failed");
        throw ex; // catchした例外をthrow
    }
    System.out.println("Finish!!");
}
```

catchした、例外を呼び出し元へ例外を通知する場合は、以下のようにcatchした例外をthrowします。

```java
throw ex;
```
そうすると、このメソッドは`FileNotFoundException`をthrowする可能性のあるメソッドとなるため、メソッド定義としてthrows句を追加します。
```java
public void hoge() throws FileNotFoundException {
```
こうすることで、hogeメソッドは、`FileNotFoundException`をthrowするメソッドとなります。

流れを表すとこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/17f32619-1870-f51e-3ae0-977bfc4df845.png)

#### finally句で、正常時・異常時でも必ず処理させたい
上記の例では、例外が発生して、例外をthrowすると、後続の処理が続行されずに、  
`System.out.println("Finish!!");`が実行されません。

例外が発生しても、していなくても、実行させたい処理がある場合は、finallyブロック内に処理を記載してあげます。
```java
finally {
	System.out.println("Finish!!");
}
```
こうすることで、例外が発生しても、していなくても最終的に、必ず`System.out.println("Finish!!")`が実行されます。

流れを表すとこんな感じ。

##### 例外が発生した場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/80d6998d-6f91-7bc4-2615-8d86cfd98eff.png)

##### 例外が発生しなかった場合（正常系）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/2ca3dcf8-1531-1955-5acf-f94e4b35cc19.png)


## try-catch句を使わずにthrowsに定義する
```java
public void hoge() throws FileNotFoundException {
    FileReader fileReader = new FileReader("/hoge.txt");
    System.out.println("Success");
    System.out.println("Finish!!");
}
```
この書き方は、try-catch句を使わずに、`throws FileNotFoundException`のみを定義します。
こうすることで、`new FileReader("/hoge.txt")`で、`FileNotFoundException`が発生した際に、すぐさまメソッドを終了し、呼び出し元へ`FileNotFoundException`をthrowします。
`Success`のコンソール出力は実行されません。

全く使わないということはないですが、例外が発生した際に、適切なログ出力を施すことを考えると現実的にはtry-catch句で例外処理を行った上で、throwしてあげたりするのが現実出来かと思います。

流れを表すとこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2585352/932e26a2-69a0-629f-5509-c3f29545d50b.png)


# 5. まとめ
* 検査例外（RuntimeExceptionを除くExceptionのサブクラス）がthrowされるメソッドは、例外処理を施す
* 例外処理はtry-catch句で処理するか、throws句に定義させる
* try-catchでcatchさせたとしても、throwさせる場合は、throws句も定義する