【Java】例外処理

#例外処理とは
* アプリ実行時に発生するエラーに対応する
    * 狭義の例外：開発者の責任では可否できないエラー
        * ファイルが存在しない
        * 接続先DBが停止していた
    * Javaではこれらのエラーに対応必須
* **例外は型（クラス）**である
    * **一般的な例外を上位クラス、個々の状況の例外を下位クラス**としたクラス階層として表す

#try...catch構文
* **tryブロック**：本来の処理
* **catchブロック**：例外の型によって呼ばれる
    * 例外発生=**例外がスロー**される 
    * 指定された**例外変数（eやex）**を介して例外オブジェクトにアクセス
    * ブロック単位でスコープを持つので複数catchブロックがあっても同じ変数名(e)でOK
* 例外クラスのメソッド
    * **getMessage()**：エラーメッセージ取得
    * **getLocalizedMessage()**：ローカライズ対応したエラーメッセージ取得
    * **getCause()**：エラー原因を取得
    * **getStackTrace()**：スタックトレース取得
    * **printStackTrace()**：スタックトレース出力

```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class TryBasic {

  public static void main(String[] args) {
    try {
      var in = new FileInputStream("C:/data/nothing.gif");
      var data = -1;
      while ((data = in.read()) != -1) {
        System.out.printf("%02X ", data);
      }
    //FileInputStreamクラスでFileNotFoundException例外をスロー
    } catch (FileNotFoundException e) {
      System.out.println("ファイルが見つかりませんでした。");
    } catch (IOException e) {
      //指定された例外変数（e）を介して例外オブジェクトにアクセス
      e.printStackTrace();
    }
  }
}
```
##スタックトレース
* **例外発生までに経たメソッドの履歴**
* エントリポイントであるmainメソッドを基点に呼び出し順に記録
* 意図しないメソッドが呼ばれてないか、過程が間違ってないか確認

##finallyブロック
* 例外の有無によらず最終的に実行されるブロック
* tryブロックで利用したリソースの処理など
* finallyブロックは一個だけ

```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class TryFinally {

  public static void main(String[] args) {
    FileInputStream in = null;
    try {
      in = new FileInputStream("C:/data/nothing.gif");
      var data = -1;
      while ((data = in.read()) != -1) {
        System.out.printf("%02X ", data);
      }
    } catch (FileNotFoundException e) {
      System.out.println("ファイルが見つかりませんでした。");
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      //例外の有無によらずファイルクローズ
      try {
        if (in != null) {
          in.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}
```
#try-with-resource構文
* try...finally構文よりもシンプルなので推奨
* **tryブロックを出るタイミングでリソース自動解放するのでfinally不要！**
* 注意：
    * **AutoCloseableインターフェースを実装すること**
        * Reader/Writer/InputStream/OutputStream/Connection/Statement/ResultSet
    * **リソースの解放順序が変わる**
        * 従来のtry...finally構文ではcatchブロック時点でリソース生きてた
        * try-with-resourceではtryブロックを出るとリソース解放
    * **リソーススコープが異なる**
        * try...finally構文ではブロックをまたいでリソース処理するのでリソース変数はtryブロックの外で宣言
        * try-with-resourceではスコープがtryブロック配下に限定（Java9以降ではtryブロック外で宣言されたリソース引用可能）
        * `var input = new FileInputStream(...)`
        * `try(input){...}`

```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class TryWithResources {

//ブロック先頭でリソース宣言
  public static void main(String[] args) {
    try (var in = new FileInputStream("C:/data/hogee.gif")) {
      var data = -1;
      while ((data = in.read()) != -1) {
        System.out.printf("%02X ", data);
      }
    //tryブロックを出るとリソース自動解放
    } catch (FileNotFoundException e) {
      System.out.println("ファイルが見つかりません。");
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
```

#例外クラスの階層構造
* クラス階層という観点では、全部の例外/エラークラスは**Throwableクラス**を頂点とした階層ツリーに属する
    * **Errorクラス**：致命的エラー 　　　     　　　---- **【例外処理しない】**
        * 配下のエラーは**エラー例外**
        * IOErrer：重大な入出力エラー、VirtualMachineError：仮想マシンレベルでの障害
    * **Exceptionクラス**     　　　　　　　　　　　 ---- **【例外処理する】**
        * **RuntimeExceptionクラス**　　　　　　---- **【例外処理任意】**
            * 配下のエラーは**非検査例外**（=実行時例外）
            * IllegalArgumentException：不正な引数、NullPointerException：オブジェクトがnull
        * その他配下のエラーは**検査例外**
            * FileNotFoundException：ファイル存在なし、SQLException：DBアクセス時の問題
            
#例外処理の注意
* **Exceptionで捕捉しない**
    * `catch(Exception e)`は全部の例外を捕捉
        * IOExceptionより具体的なFileNotFoundException 
* **マルチキャッチ活用する**
    * 複数の例外に対して同じ処理をする
    * `例外1|例外2`（いずれかの場合）
* **catchブロック順**
    * 複数ある場合、先頭の記述が優先
    * **下位の例外を先に、上位の例外を後に記述**


```java
//マルチキャッチ
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

public class TryMulti {

  public static void main(String[] args) {
    try {
      var in = new FileInputStream("C:/data/nasi.gif");
      var data = -1;
      while ((data = in.read()) != -1) {
        System.out.printf("%02X ", data);
      }
      var uri = new URI( "https://www.neko.example.com");
      System.out.println(uri.toString());
    //マルチキャッチ
    } catch (IOException | URISyntaxException e) {
      System.out.println("ファイルにアクセスできません。");
      e.printStackTrace();
    }
  }
}
```

#例外をスロー
* 標準ライブラリのスロー以外に**開発者がスローできる**
* `throw 例外オブジェクト'で任意の例外オブジェクトを渡せる
* **スローする可能性のある例外をthrows句で宣言**
    * throw：例外をスローする
    * throws：メソッド/コンストラクタで宣言

```java
public FileInputStream(File file) throws FileNotFoundException{
//略
``` 

##例外スローの注意
* **Exceptionをスローしない**
* **検査例外/非検査例外を適切に選択**
* 出来るだけ**標準例外**を使う
    * **NullPointerException**：オブジェクトがnull
    * **IndexOutOfBoundsException**：配列インデックスが範囲外
    * **UnsupportedOperationException**：要求操作が未サポート
    * **IllegalArgumentException**：メソッドに渡された引数に問題
    * **IllegalStateException**：意図した状態でないときにメソッド呼び出した
    * **ArithmeticException**：計算で例外的な条件が発生
    * **NumberFormatException**：数値形式正しくない
* privateメソッドは**assert命令を使う**
    * 条件式がfalseの時にAssertionError例外
    * javaコマンドで`-ea`オプションを明示した時のみ動作

```java
public class AssertBasic {
  private static double getTrapezoidArea(double upper, double lower, double height) {
    //引数0以下の場合に例外発生
    assert upper > 0 && lower > 0 && height > 0;
    return (upper + lower) * height / 2;
  }
  public static void main(String[] args) {
    System.out.println(AssertBasic.getTrapezoidArea(-2, 4, 0));
  }
}
```

#例外再スロー
* その場で処理せず**処理は呼び出し元に委ねること**
* 基本、その場で個別処理ではなく、個々の処理を呼びアプリの流れそのものを制御する**上位コードで処理すべき**
* 例外型を賢く判定できるようになっている（Java7~）
    * throwsで宣言するのはException型のみでOK
    * マルチキャッチ不要

```java
public class TryRethrow {
//MySampleException, MyLibExceptionでrethrowメソッド発生する可能性
  public static void rethrow(boolean flag) throws MySampleException, MyLibException {
    try {
      if (flag) {
        throw new MySampleException();
      } else {
        throw new MyLibException();
      }
    //Exception型で受けてそのまま再スロー
    } catch (Exception e) {
      throw e;
    }
  }
}
```

#翻訳例外
* **個々の例外を一旦補足して上位のアプリの例外として束ね再スロー**
* 例外をアプリ独自の例外にまとめることができる
* 呼び出し元で個々の例外を意識せず、**より高いレベルの例外を意識すればいい**
* 翻訳された上位例外から本来の原因を取得するには**`getCause`メソッド**
    * `var ex = e.getCause();`

```java

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;

//翻訳例外でMySampleExceptionのみ意識すればいい
public class UseTrans {
  public void readHttpPages() throws MySampleException {
    try (var reader = Files.newBufferedReader(Paths.get("C:/data/link.txt"))) {
      var line = "";
      while ((line = reader.readLine()) != null) {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder().uri(URI.create(line)).build();
        var res = client.send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println(res.body());
      }
    } catch (IOException | InterruptedException e) {
      throw new MySampleException(e);
    }
  }
}
```

#独自の例外クラス
* 例外クラスはアプリで定義可能
* **Exception（派生クラス）を継承すること**
* **コンストラクタをオーバーライドすること**
    * Exceptionで定義されたコンストラクタを再定義しsuper呼び出しでOK
* 独自のフィールドやメソッドの実装を持った例外も可能


```java
//Exception（派生クラス）を継承
public class MySampleException extends Exception {
  //コンストラクタをオーバーライド
  public MySampleException() {
    super();
  }
  public MySampleException(String message) {
    super(message);
  }
  public MySampleException(String message, Throwable cause) {
    super(message, cause);
  }
  public MySampleException(Throwable cause) {
    super(cause);
  }
}
```