Javaにおける例外処理の方法をまとめてみました


# はじめに
現在、私が進行しているJavaの勉強会でまとめた内容です。
内容に不正確な点があれば、ご指摘いただけるとありがたいです。



- 韓国人として、日本語とコンピュータの勉強を同時に行うために、ここに文章を書いています
- 翻訳ツールの助けを借りて書いた文章なので、誤りがあるかもしれません

# try-catch
- `try`文はJavaで例外を処理するために使用されます
- `try`文を使用すると、例外が発生する可能性のあるコードを囲み、例外が発生した際に適切に処理することができます

```java
try {
    // 例外が発生する可能性のあるコード
} catch (例外タイプ1 変数名) {
    // 例外タイプ1を処理するコード
} catch (例外タイプ2 変数名) {
    // 例外タイプ2を処理するコード
} finally {
    // 例外の発生に関係なく必ず実行されるコード（オプション）
}
```
### try
- 例外が発生する可能性のあるコードを含みます
- 例外が発生しない場合、`try`の後のコードが実行されます

### catch
- 特定の例外を処理するためのブロックです
- 複数の`catch`を持つことができ、順番に検査されます

### finally
- 例外の発生に関係なく実行されます

### 単一キャッチ句 (Uni-catch Clause)
- 特定の単一エラーのみを処理します

```java
public class Main{
    public static void main(String[] args) {
        try {
            int result = 1 / 0; 
            } catch (ArithmeticException e) {
                System.out.println("0で割ることはできません: " + e.getMessage());
        }
    }
}
```

### マルチキャッチ句 (Multi-catch Clause)
- Java 7で導入された文法
- 複数の例外を同時に処理
```java
public class Main {
    public static void main(String[] args) {
        try {
            // 主なロジック
        } catch (NullPointerException | ArithmeticException e) {
            System.out.println("例外発生: " + e.getClass().getSimpleName() + " - " + e.getMessage());
        }
    }
}
```
- 複数の共通エラー処理ロジックを1つにまとめることで、繰り返しコードを削減できます

## try-with-resources
- close()はAutoCloseableインターフェースを実装したオブジェクトで使用することができます

:::note 
**AutoCloseable**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/6cdf6d45-a449-d70b-1714-bddb22b78723.png)
- ファイルやソケットハンドルのようなリソースを保持し、`close()`を使用してリソースを解放することで、リソース解放時に発生する例外やエラーを防ぐためのインターフェースです

### `close()`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/6db6c700-4c93-60d7-f264-2e231c8e949d.png)
- このリソースを閉じるために使用されるメソッドです
- **try-with-resources**文で記述された場合、自動的に呼び出されます
- `close()`の実装では`InterruptedException`をスローしないことを**強く推奨**します
   - なぜなら、スレッドの中断状態と相互作用し、ランタイムの誤動作が発生する可能性が高くなるためです
- `Closeable`とは異なり、冪等性は必須ではありません
- ただし、複数回呼び出されると副作用が発生する可能性があるため、冪等性を持たせることを**推奨**します
::: 

### なぜ使用するのか 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/d6372c14-3102-211f-2837-fbd2ad9112b8.png)

上記のコードには致命的な問題があります。なぜなら

```java
finally {
    br.close();
    fis.close();
}
```
`finally`内で`close()`メソッドを呼び出すと、`NullPointerException`が発生する可能性があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/f9a14f26-f0dc-27c8-c6a3-4af53d36225d.png)
このように`null`チェックを行っても、完全に安全とは言えません。
```java
finally {
    if (br != null) {
        br.close();
    }
    if (fis != null) {
        fis.close();
    }
}
```
もし`br = new BufferedReader(new InputStreamReader(fis));`の`br.close()`で例外が発生した場合、
`fis = new FileInputStream("test.txt");`のリソースの解放は保証されません。

この問題を解決するには`try-catch`文で囲む必要があります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/0acd9443-38a4-cb82-0576-f4b83c93a2e5.png)

コードが非常に煩雑になってしまいます。
リソース管理が必要なリソースが増え、`close()`を呼び出すオブジェクトが多くなるほど、管理が難しくなります。

### try-with-resources文を使いましょう

`try-with-resources`を使えば、開発者が手動で管理しなくても自動的に`close()`が呼び出されます。  
その結果、はるかに簡潔で安全なコードが実現できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/9b31fa20-c13e-fca3-e954-b1cafd30fdeb.png)



:::note info
**try-catchとtry-with-resourcesのバイトコード比較**

### try-catch
以前に記述した`try-catch`文のコードは
```java
package org.example;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = null;
        BufferedReader br = null;
        try {
            fis = new FileInputStream("test.txt");
            br = new BufferedReader(new InputStreamReader(fis));

            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
```
次のようなコードであり、バイトコードは以下の通りです
```java
  ~/Documents/github/study-gof/build/classes/java/main/org/example on   main !1 ?4 ❯ javap -c Main                                                                                                              at  12:10:53 AM
Warning: File ./Main.class does not contain class Main
Compiled from "Main.java"
public class org.example.Main {
  public org.example.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.io.IOException;
    Code:
       0: aconst_null
       1: astore_1
       2: aconst_null
       3: astore_2
       4: new           #7                  // class java/io/FileInputStream
       7: dup
       8: ldc           #9                  // String test.txt
      10: invokespecial #11                 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
      13: astore_1
      14: new           #14                 // class java/io/BufferedReader
      17: dup
      18: new           #16                 // class java/io/InputStreamReader
      21: dup
      22: aload_1
      23: invokespecial #18                 // Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;)V
      26: invokespecial #21                 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      29: astore_2
      30: aload_2
      31: invokevirtual #24                 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      34: dup
      35: astore_3
      36: ifnull        49
      39: getstatic     #28                 // Field java/lang/System.out:Ljava/io/PrintStream;
      42: aload_3
      43: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      46: goto          30
      49: aload_2
      50: ifnull        65
      53: aload_2
      54: invokevirtual #39                 // Method java/io/BufferedReader.close:()V
      57: goto          65
      60: astore_3
      61: aload_3
      62: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
      65: aload_1
      66: ifnull        81
      69: aload_1
      70: invokevirtual #47                 // Method java/io/FileInputStream.close:()V
      73: goto          81
      76: astore_3
      77: aload_3
      78: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
      81: goto          191
      84: astore_3
      85: aload_3
      86: invokevirtual #50                 // Method java/lang/Exception.printStackTrace:()V
      89: goto          191
      92: astore_3
      93: aload_3
      94: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
      97: aload_2
      98: ifnull        113
     101: aload_2
     102: invokevirtual #39                 // Method java/io/BufferedReader.close:()V
     105: goto          113
     108: astore_3
     109: aload_3
     110: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
     113: aload_1
     114: ifnull        129
     117: aload_1
     118: invokevirtual #47                 // Method java/io/FileInputStream.close:()V
     121: goto          129
     124: astore_3
     125: aload_3
     126: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
     129: goto          191
     132: astore_3
     133: aload_3
     134: invokevirtual #50                 // Method java/lang/Exception.printStackTrace:()V
     137: goto          191
     140: astore        4
     142: aload_2
     143: ifnull        160
     146: aload_2
     147: invokevirtual #39                 // Method java/io/BufferedReader.close:()V
     150: goto          160
     153: astore        5
     155: aload         5
     157: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
     160: aload_1
     161: ifnull        178
     164: aload_1
     165: invokevirtual #47                 // Method java/io/FileInputStream.close:()V
     168: goto          178
     171: astore        5
     173: aload         5
     175: invokevirtual #44                 // Method java/io/IOException.printStackTrace:()V
     178: goto          188
     181: astore        5
     183: aload         5
     185: invokevirtual #50                 // Method java/lang/Exception.printStackTrace:()V
     188: aload         4
     190: athrow
     191: return
    Exception table:
       from    to  target type
          53    57    60   Class java/io/IOException
          69    73    76   Class java/io/IOException
          49    81    84   Class java/lang/Exception
           4    49    92   Class java/io/IOException
         101   105   108   Class java/io/IOException
         117   121   124   Class java/io/IOException
          97   129   132   Class java/lang/Exception
           4    49   140   any
          92    97   140   any
         146   150   153   Class java/io/IOException
         164   168   171   Class java/io/IOException
         142   178   181   Class java/lang/Exception
         140   142   140   any
}
```
もう少し読みやすくするために、IntelliJを使用してクラスファイルを開いてみると、 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/7be57256-7a5c-e916-3c6a-1822fd9ee0a8.png)

このような構造になっています。

### try-with-resuorse

では、`try-with-resources`を使用した場合のバイトコードはどうなるのでしょうか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/3789782a-1f3a-4914-6aa1-9d19412dd2f4.png)

このように記述されたコードは
```java
  ~/Documents/github/study-gof/build/classes/java/main/org/example on   main !1 ?4 ❯ javap -c Main                                                                                                              at  12:10:57 AM
Warning: File ./Main.class does not contain class Main
Compiled from "Main.java"
public class org.example.Main {
  public org.example.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.io.IOException;
    Code:
       0: new           #7                  // class java/io/FileInputStream
       3: dup
       4: ldc           #9                  // String test.txt
       6: invokespecial #11                 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
       9: astore_1
      10: new           #14                 // class java/io/BufferedReader
      13: dup
      14: new           #16                 // class java/io/InputStreamReader
      17: dup
      18: aload_1
      19: invokespecial #18                 // Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;)V
      22: invokespecial #21                 // Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
      25: astore_2
      26: aload_2
      27: invokevirtual #24                 // Method java/io/BufferedReader.readLine:()Ljava/lang/String;
      30: dup
      31: astore_3
      32: ifnull        45
      35: getstatic     #28                 // Field java/lang/System.out:Ljava/io/PrintStream;
      38: aload_3
      39: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      42: goto          26
      45: aload_2
      46: invokevirtual #39                 // Method java/io/BufferedReader.close:()V
      49: goto          70
      52: astore_3
      53: aload_2
      54: invokevirtual #39                 // Method java/io/BufferedReader.close:()V
      57: goto          68
      60: astore        4
      62: aload_3
      63: aload         4
      65: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      68: aload_3
      69: athrow
      70: aload_1
      71: invokevirtual #48                 // Method java/io/FileInputStream.close:()V
      74: goto          93
      77: astore_2
      78: aload_1
      79: invokevirtual #48                 // Method java/io/FileInputStream.close:()V
      82: goto          91
      85: astore_3
      86: aload_2
      87: aload_3
      88: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      91: aload_2
      92: athrow
      93: goto          101
      96: astore_1
      97: aload_1
      98: invokevirtual #51                 // Method java/io/IOException.printStackTrace:()V
     101: return
    Exception table:
       from    to  target type
          26    45    52   Class java/lang/Throwable
          53    57    60   Class java/lang/Throwable
          10    70    77   Class java/lang/Throwable
          78    82    85   Class java/lang/Throwable
           0    93    96   Class java/io/IOException
}
```
次のような形になっており、  
IntelliJを通じて確認すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/ae95e158-4190-3e89-50e5-42f86b7afeab.png)

このように、自分が記述していなかった`try-catch`文で囲まれた後に、`close()`メソッドが呼び出されていることが確認できます。

これは、`javac`コンパイラがコンパイルの過程で`try-with-resources`文のリソース管理ロジックを自動的に追加するためです。

### 結論
- `try-with-resources`文は、コンパイル後に自動で`try-catch`文で囲まれた`close()`文が生成されます
- 結果的に、開発者が手動で定義する場合と`try-with-resources`を使用する場合のパフォーマンスや機能の違いは全くありません
- したがって、`try-with-resources`を使用することをお勧めします
:::

### throw
- 例外を直接発生させる際に使用します
- 例外オブジェクト（`Throwable`参照）を生成してスローします
```java
public void isUser(User user){
    if(!isValidUser){
        throw new IllegalArgumentException("認証されたユーザーではありません");
    }
}
```
### throws
- メソッド内で発生する可能性のある例外を宣言する際に使用するキーワードです
- メソッドを呼び出す箇所（上位メソッド）で例外処理を行うように伝達します

```java
public void readFile(String filename) throws FileNotFoundException {
    FileReader file = new FileReader(filename);
      // ファイル処理ロジック
}
```

- また、複数の例外を宣言することもできます


```java
public void someMethod() throws FileNotFoundException, SQLException {
     // メソッドの内容
}
```

# Throwableの階層構造
- `Throwable`はすべてのエラーと例外の最上位クラスであり、`Object`を継承しています
```markdown
Throwable
├── Error
│   ├── VirtualMachineError
│   │   ├── StackOverflowError
│   │   ├── OutOfMemoryError
│   │   ├── InternalError
│   │   └── UnknownError
│   ├── LinkageError
│   │   ├── NoClassDefFoundError
│   │   ├── ClassFormatError
│   │   ├── UnsupportedClassVersionError
│   │   ├── VerifyError
│   │   └── UnsatisfiedLinkError
│   ├── AssertionError
│   ├── ThreadDeath
│   ├── ExceptionInInitializerError
│   └── NoSuchFieldError
└── Exception
    ├── RuntimeException
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   │   └── NumberFormatException
    │   ├── IllegalStateException
    │   ├── ClassCastException
    │   ├── IndexOutOfBoundsException
    │   │   ├── StringIndexOutOfBoundsException
    │   │   └── ArrayIndexOutOfBoundsException
    │   ├── UnsupportedOperationException
    │   └── ArithmeticException
    ├── IOException
    │   ├── FileNotFoundException
    │   ├── EOFException
    │   ├── InterruptedIOException
    │   ├── UnsupportedEncodingException
    │   ├── CharConversionException
    │   ├── FileSystemException
    │   ├── SocketException
    │   │   ├── BindException
    │   │   ├── ConnectException
    │   │   ├── NoRouteToHostException
    │   │   ├── PortUnreachableException
    │   │   └── SocketTimeoutException
    │   └── UnknownHostException
    ├── SQLException
    ├── ClassNotFoundException
    ├── CloneNotSupportedException
    ├── InstantiationException
    ├── IllegalAccessException
    ├── InterruptedException
    ├── NoSuchMethodException
    ├── NoSuchFieldException
    ├── ReflectiveOperationException
    ├── TimeoutException
    ├── ParseException
    ├── MalformedURLException
    └── URISyntaxException
```
- "GPTを通じて少し作ってもらいました（多すぎて...）"

## Throwableの主なメソッド（エラーメッセージの出力方法）
`Throwable`で定義されているメソッドの中から、出力方法を調べてみました。

### getMessage()
- 例外の原因のみを簡潔に出力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/e8c8da5f-6400-2a4b-2fb3-418886026155.png)

### toString()
- 例外の原因を簡潔に出力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/6831f44a-efc5-6b49-c4be-b9c03aec553e.png)

### printStackTrace()
-  例外メッセージと呼び出し関係を一緒に出力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/b9c83dba-b964-64bf-bf0f-213c0aecf4d9.png)

# Javaのエラーと例外
Javaで発生する問題は、大きく2つに分けることができます。

## Error
- エラーはアプリケーションが復旧できない深刻な問題です
- 開発者がこれを解決する方法は（ほぼ）ありません

### 注意点
**`Error`を`catch`してはいけません**  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/898da770-59b1-cf9e-887f-1f3198f79b0c.png)

`Error`クラスは以下のように定義されています。

この内容を簡単に要約すると、  
`Error`は本質的にシステムに深刻な問題が発生（復旧不可能）したため、復旧することはできません。  
そのため、`catch`するべきではありません。

また、後に登場する`unchecked exception`として分類されるため、`throws`で宣言する必要もありません。

https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html


# Exception
Javaの`Exception`には、大きく2つのケースがあります。

## Checked Exceptions
- コンパイル時に例外処理を強制される`Exception`を指します
- `throws`でスローするか、`try-catch`文を使用する必要があります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/050b36c2-2c85-7a4b-3c50-e58a09b017d6.png)

以下のように記述した場合、
```java
error: unreported exception IOException; must be caught or declared to be thrown
        throw new IOException();
        ^
```
次のようなメッセージが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/0de8d8e3-a6a0-0019-929b-d4f98acfc2c4.png)

以下のように`throws`や`try-catch`を使用することで解決できます。

### 注意点
- ループ内でChecked Exceptionを処理するのは避けるべきです
```java
for (String filePath : filePaths) {
    try {
        readFile(filePath);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```
なぜなら、例外処理にはコストがかかるため、パフォーマンスが低下する可能性があるからです。

## Unchecked Exceptions (RuntimeException)

- `RuntimeException`およびそのサブクラスは、実行時に発生する例外を定義します
- 主にロジックのエラーや不適切なAPIの使用によって発生します

- コンパイラによるチェックが行われないため、例外処理が強制されません
- `try-catch`で処理するか、`throws`で宣言しなくてもコンパイル可能です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3815412/98df2337-92ed-9c8e-390f-495e80c2db8b.png)


# カスタム例外 (Custom Exception)
- コードを書いていると、ドメインルールに基づく例外を発生させる必要性を感じる場合があります

- これはドメインルールとして特別な管理が必要なため、通常以下のようなネーミングでカスタム例外を作成します
- 
```java
{ドメインルール}Exception
```
このようなネーミングでカスタム例外を作成することができます。

たとえば、「ユーザーではない」という状況は、Javaコードそのものには問題がありません。  
しかし、私たちがエラーとして定義し、統合的に管理する必要があると感じた場合は、カスタム例外を作成することができます。

```java
public class NotUserException extends RuntimeException {
   public NotUserException() {
       super("ユーザーではありません。");
   }

   public NotUserException(String message) {
       super(message);
   }

   public NotUserException(String message, Throwable cause) {
       super(message, cause);
   }
}
```

### 注意点
- カスタム例外を無分別に使用すると、不要なクラスが増え、管理が困難になります
- 「Effective Java」という本でも、標準例外が状況に適している場合は再利用することを推奨しています
- Javaの標準例外で十分に定義できるものをカスタム例外として作成すると、他の開発者が見たときにそのコードが何をするものか理解できなくなります

以下のような場合にカスタム例外を作成することをお勧めします：
1. コアドメインやビジネスロジックを含む例外（文書化が必要な例外）
2. 例外に追加情報や機能が必要な場合

協業やビジネスの観点から例外を考えることで、どのような場合に使用すべきかをより明確に理解できるようになるでしょう。


#### リファレンス
https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html#jls-14.20
https://sujl95.tistory.com/62
https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
https://www.amazon.co.jp/-/en/Joshua-Bloch/dp/0134685997