Java の 例外

Java の例外について情報をまとめる

# 例外の種類（継承関係）
Java の例外クラス Throwable, Error, Exception, RuntimeException の階層を整理
`Exception` を継承した例外は検査例外となり`throws` や `try-catch` して例外処理しないとコンパイルエラーとなる
`RuntimeException` を継承した例外は非検査例外となり例外処理しなくてもコンパイルエラーにならない
`Throwable`, `Error` を明示的に使うケースあまりない（使う時はどんな例外も絶対に Java の外に出したくない時など．．．）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/bbca4a07-fe43-138a-29f8-696f17dbb19b.png)

|クラス|概要|例|
|:--|:--|:--|
|Throwable|例外の基底クラス<br>throws/try-cathc必要<br>|-|
|Error|致命的エラー<br>throws/try-cathc不要<br>|StackOverflowError<br>OutOfMemoryError
|**Exception**|検査例外<br>throws/try-cathc必要（ないとコンパイルエラー）|IOException<br>SQLException|
|**RuntimeException**|非検査例外<br>throws/try-cathc不要<br>検査例外をラップしたExceptionを作ることがある|NullPointerException<br>NumberFormatException|

#### Exception 検査例外
呼び出し元に何らかの例外処理を強制する例外
`throws` や `try-catch` しないとコンパイルエラーになる
例外を処理しない時は例外情報を失わないために `throws` で呼び出し元に例外を投げる

- メソッド宣言 throws で呼び出し元に例外を投げる
```Java
public void method() throws IOException {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
}
```
- 処理内で try-catch して例外処理
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) {
    // 例外処理
    ...
}
``` 

#### RuntimeException 非検査例外
呼び出し元に例外処理を強制しない例外
例外処理をしなくてもコンパイルエラーにならない（実行時例外になる）
検査例外をラップして非検査例外にすることがある
そうすることで呼び出し元のメソッド定義を変えずにコンパイルエラーにならずに処理できる

- 検査例外を非検査例外でラップ
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) { 
    throw new RuntimeException(e);
}
```
# 例外処理の文法
Java の例外処理の文法を整理
文法としては「メソッド宣言 `throws`」、「処理内 `try-catch`」の2つある
`Exception` 検査例外 が発生するときは記載がないとコンパイルエラーになる
`RuntimeException` 非検査例外のときは記載不要だが明示的に記載し JavaDoc にコメントを残すことで例外を他の開発者に伝えることができる

## メソッド宣言 `throws`
呼び出し元のクラスで例外処理を行ってもらうときの記載方法
発生する例外に対して `throws` に記載していく
```Java
public void method() throws IOException, ParseException { // 「,」区切りで複数記載OK 
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    Object text = new DateFormatter().stringToValue("test");
    ...
}
```
## 処理内 `try-catch-finally`
処理内で例外処理を行うときの記載方法
発生する例外に対して `catch` に記載していく
`catch` した後の例外処理で `throw` することもできる
その場合、メソッド宣言 `throws` と合わせて記載する
例外発生に関係なく必ず実行したい処理があれば `finally` の中に記載する
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    Object text = new DateFormatter().stringToValue("test");
    Thread.sleep(1);
    ...
} catch (IOException | ParseException e) { // 「|」で複数記載OK
    // 例外処理
    ...
} catch (InterruptedException e) { // 「catch」は複数記載OK
    // 例外処理
    ...
    // throw することも可能（その場合、メソッド宣言 throws も必要）
    throw e;
} finally {
    // 例外発生に関係なく必ず実行する処理
    ...
}
```
### - リソースクローズ `try-with-resources`
ファイル、DBなどの外部リソースにアクセスするときに接続を断つために `close` 処理を実行する必要がある
`close`しないと接続が開いた状態が続くことがあり予期せぬバグにつながることがある
Java では `try-with-resources` を使うことで対応できる（`AutoCloseable`の実装クラスのみ）
```Java
// try() の中で変数宣言したものが close 対象、「;」で複数行記載OK
try (BufferedReader br = Files.newBufferedReader(Paths.get("xxx"))) {
    ...
} catch (IOException e) {
    ...
}
```
### - 例外ラップ
例外を別の例外にして `throw` するときは元の例外をラップして例外情報を失わないようにする
例外をラップしないで別の例外を生成すると元の例外情報が失われてしまいバグの原因が分からなくなってしまう
例外を別の例外にラップするするケースとしては以下2つが考えられる
- アプリ（機能）独自の例外にして例外処理を統一する
- 検査例外を非検査例外にして呼び出し元に影響(コンパイルエラー)が出ないようにする
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) { 
    // e(例外) を別の例外を生成するときに引数として渡す
    throw new RuntimeException(e);
    // throw new RuntimeException("error message", e);
}
```

# 例外処理でダメなこと
例外処理において例外を隠蔽（握り潰す）したり変えてしまうと原因の特定ができなくなってしまう
また広範囲(`Excetion`など)な例外を  `throws` や `catch` すると想定外の例外でも処理してしまいバグに気づかなくなってしまう
例外処理でやらないほうがよいことを記載

## 例外を隠蔽(握り潰す)
例外を `catch` した後に処理しないで終わると隠蔽したことになる
例外処理をしないクラスでは `catch` しないで呼び出し元に `throw` する（メソッド宣言`throws`のみでOK）
例外処理をするときでも `catch` して処理を行った後に呼び出し元に `throw` する（メソッド宣`throws` + `catch` した処理内で `throw` が必要）
意図的に `throw` しない時は log出力など例外情報を記録した方が良い
何も情報を残さないと想定外の例外が放置されてしまい別の問題を引き起こす可能性がある

- 隠蔽(握り潰す)
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) {
    // 処理しないで握り潰す
    // 意図的にやるときでも log 出力してほしい、明らかに不要なときでもコメントに意図を記載
}
``` 
- 呼び出し元に throw（メソッド宣言 throws のみ） 
```Java
public void method() throws IOException {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
}
```
- 呼び出し元に throw (catch して処理して throw)
```Java
public void method() throws IOException {
    try {
        List<String> lines = Files.readAllLines(Paths.get("xxx"));
        ...
    } catch (IOException e) {
        ...
        throw e; 
        // 別の Exception にラップ(設定)するでもOK 
        // throw new XxxException(e);
        // throw new XxxException("messeage", e);
    }
}
```

## 例外を消失
`catch` した例外をラップ(設定)しない 'Exception' を生成して `throw` すると例外情報を失うことになる
例外処理で新しく Exception を生成するときは基本的には  `catch` した `Exception` をラップ(設定)し例外情報を失わないようにする
意図的にラップ(設定)しない時は log出力など例外情報を記録した方が良い
何も情報を残さないと想定外の例外が放置されてしまい別の問題を引き起こす可能性がある

- 消失
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) {
    // e をラップ(設定)しない Exception 生成で元の例外を消失
    // 意図的にやるときでも log 出力してほしい、明らかに不要なときでもコメントに意図を記載
    throw new XxxException("message");
}
``` 
- 例外をラップ(設定)
```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) {
    // e をラップ(設定)すればOK
    throw new XxxException("message", e);
}
``` 
## 広範囲な例外指定
`throws` や `catch` に `Exception` などの広範囲な例外クラスを使うと想定外の例外に対しても処理してしまう
そのため想定した例外以外が発生したときに例外が正しく処理されないことがある
とくに意図的に隠蔽などをおこなった箇所でやっていると例外情報が失われてしまう
- 余計な例外処理
```Java
// Exception ではなく IOException など発生する例外のみにする
public void method() throws Exception {
    try {
        List<String> lines = Files.readAllLines(Paths.get("xxx"));
        ...
    } catch (Exception e) {
　　　　// Exception ではなく IOException など発生する例外のみにする
        ...
        throw e; 
    }
}
```

## 例外処理でスタックトレース出力のみ
`catch` した例外処理の中で `e.printStackTrace()` のみしか行わないとアプリ停止や設定によって例外情報が失われてしまう
結果として隠蔽(握り潰す)と同じことになる
教科書などのサンプルだと多いが実際のアプリではスタックトレース出力ではなくログ出力していることが多い

```Java
try {
    List<String> lines = Files.readAllLines(Paths.get("xxx"));
    ...
} catch (IOException e) {
    // これのみだと隠蔽と同じことになる
    // throw して呼び出し元で処理するか log 出力して例外情報を残してほしい
    e.printStackTrace;
}
```