Javaの例外処理をマスターする

## はじめに
この記事ではJavaの例外処理に関する基本的な内容について説明します！
私はエンジニアになる前も少しJavaに触れていましたが、その頃は例外処理について何も理解していませんでした…。
つい最近、ようやく例外処理の使い方や重要性について理解できたのでここにまとめてみようと思います！

## そもそもJavaの例外処理とは？
Javaの例外処理とは、プログラムの実行中に起こる予期せぬエラー（例外）に対処し、プログラムの正常な流れを維持するための仕組みです。これにより、エラーが発生したときにプログラムが順調に動作を続けられるようになります。
つまり、例外が起きたときはあらかじめ用意しておいた処理をやってね～と指定することでプログラミングが動き続けるというだけです。

## 例外処理の具体例
Javaでは主に以下の構文とキーワードを使って例外処理を実施します。

## 1: try-catch
この構造を使用して、例外が発生しうるコードを囲みます。tryブロックの中で例外が発生すると、プログラムの制御は対応するcatchブロックに移り、指定された例外を処理するコードが実行されます。

try-catch文を使用する簡単な例を以下に示します。
```
public class TryCatchExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            // 例外が発生する可能性のあるコード
            // 配列の範囲を超える要素にアクセスしようとする
            System.out.println(numbers[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            // ArrayIndexOutOfBoundsException例外をキャッチして処理
            System.out.println("配列のインデックスが範囲外です!");
        }
        System.out.println("プログラムの実行を続けます。");
    }
}
```

このコードでは、配列numbersにアクセスしていますが、配列の範囲（インデックス2まで）を超えるインデックス3にアクセスしようとしています。これによりArrayIndexOutOfBoundsExceptionが発生します。tryブロック内で例外が発生すると、直ちにcatchブロックが実行され、例外に対するカスタムメッセージが表示されます。

このプログラムを実行すると、次のような出力が得られます。

```
配列のインデックスが範囲外です!
プログラムの実行を続けます。
```
例外処理が適用されているため、例外が発生してもプログラムは停止せず、最後の行（System.out.println（"プログラムの実行を続けます。”））まで正常に実行を続けることができました！

## 2: finally
これはtry-catchブロックにオプションで追加することができ、例外の有無にかかわらず実行されるコードを含みます。

先ほどのtry-catch文にfinallyを追記した例を以下に示します。
```
public class TryCatchExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            // 例外が発生する可能性のあるコード
            // 配列の範囲を超える要素にアクセスしようとする
            System.out.println(numbers[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            // ArrayIndexOutOfBoundsException例外をキャッチして処理
            System.out.println("配列のインデックスが範囲外です!");
        } finally {
            // 例外の発生に関わらず実行されるコード。オプション
            System.out.println("例外の発生に関わらず、このブロックは実行されます。");
        }

        System.out.println("プログラムの実行を続けます。");
    }
}
```

finallyブロックはオプションであり、例外の有無にかかわらず実行されるコードを含むことができます。

このプログラムを実行すると、次のような出力が得られます。

```
配列のインデックスが範囲外です!
例外の発生に関わらず、このブロックは実行されます。
プログラムの実行を続けます。
```
finallyブロック内のコードについても問題なく実行されました！

しかし、ここで問題になるのがfinallyブロックの必要性についてです。
先ほどのtry-catch文の例においても例外処理を施しているため問題なく最後までコードが実行されています。
###### じゃあfinallyブロック内のコードも最後に書けばよくない？
そう思いませんでしたか？
実は違います。
finallyブロックは例外の有無にかかわらず実行される。これはcatchブロックで例外が起きた際にも適用されます。

catchブロックで例外が起きる例を以下に示します。
```
import java.util.*;

public class TryCatchExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            // 例外が発生する可能性のあるコード
            // 配列の範囲を超える要素にアクセスしようとする
            System.out.println(numbers[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            // catchブロックでも例外を発生させる
            System.out.println(numbers[3]);
        } finally {
            // 例外の発生に関わらず実行されるコード。オプション
            System.out.println("例外の発生に関わらず、このブロックは実行されます。");
        }

        System.out.println("プログラムの実行を続けます。");
    }
}
```
このプログラムを実行すると、次のような出力が得られます。
```
例外の発生に関わらず、このブロックは実行されます。
```
catchブロックで起きた例外については例外処理がされていないため、プログラミングが停止してしまい、最後の行まで実行することができません。
そんな時にも必ず実行してくれるのがfinallyブロックです。便利！！

## 3: throwsとthrow
### ・throws
throwsはメソッドシグネチャに使用され、このメソッドが特定の例外を呼び出し元に伝播させる可能性があることを示します。呼び出し元は、これらの例外を適切に処理する必要があります。

### ・throw
throwを使用するとプログラム内で意図的に例外を発生させることができます。これにより、例外が発生した時のフローをテストするか、特定の条件下でカスタム例外を発生させることができます。

throwとthrowsを用いた例を以下に示します。
```
public class TryCatchExample {
    public static void main(String[] args) {
        int[] numbers = {1, -2, 3};
        try {
            // 例外が発生する可能性のあるコード
            // validateNumberメソッドはthrows節で例外を宣言しているため、ここで処理される
            validateNumber(numbers[2]);
            System.out.println(numbers[3]);
        } catch (ArrayIndexOutOfBoundsException e) {
            // ArrayIndexOutOfBoundsException例外をキャッチして処理
            System.out.println("配列のインデックスが範囲外です!");
        } catch (Exception e) {
            // Exceptionをキャッチして処理
            System.out.println(e.getMessage());
        } finally {
            System.out.println("例外の発生に関わらず、このブロックは実行されます。");
        }
        System.out.println("プログラムの実行を続けます。");
    }

    public static void validateNumber(int number) throws Exception {
        if (number <= 0) {
            throw new Exception("数値は0より大きくなければなりません。");
        }
        System.out.println("検証された数値: " + number);
    }
}
```
上記のコードでは、numbers[2]に負の値が入っている場合、Exceptionをthrowしています。また、呼び出し元であるmainメソッドで例外処理を行いメッセージ文を表示するようにしています。
このように例外が発生する可能性のあるメソッドにはthrowsを記述する必要があります。

このプログラムを実行すると、次のような出力が得られます。
```
検証された数値: 3
配列のインデックスが範囲外です!
例外の発生に関わらず、このブロックは実行されます。
プログラムの実行を続けます。
```

## おわりに
以上、例外処理の基本的な説明でした！
少しでも例外処理の理解が深まったのであればとても嬉しいです！
ここまで読んでいただき本当にありがとうございました！