try-catchで例外処理を実装！Exceptionクラスの使い方【Java】

Javaで例外処理を実装するには、try-catch処理の中で「Exception」クラスを指定する必要がある。

・例外処理とは、
（想定内の）エラーが起きたときに行う処理のこと。

### 「try-catch文」とは
try-catch文とは、例外が発生する可能性がある処理に使うもの。
これを使うことで、例外が発生しない場合の処理と、例外が発生したときの処理を分けることができる。

さらに、finallyを使って例外の有無に関わらず、最後に必ず実行される処理を記述することができる。
```Main.java
//try-catch文の記述方法
try {
    例外が発生する可能性のある処理
} catch (例外の型 引数) {
    例外が発生した場合の処理(例外が発生しなければ行われない処理)
} finally {
    例外の有無に関わらず最後に必ず実行される処理
}
```

### try-catch文の使い方
tryブロックの中に例外が発生する可能性のある処理を記述し、catchブロックの中に例外が発生した後の処理を記述。
```Main.java
public class Main {
 
    public static void main(String[] args) {
        int result;
        result = div(5, 0);
        System.out.println("戻り値 = " + result);
    }
 
    public static int div(int num1, int num2) {
        try {
            int result = num1 / num2;
            
            return result;
 
        } catch (ArithmeticException e) {
            System.out.println("例外が発生しました。");
            System.out.println(e);
 
            return 0;
        }
    }
}
```
```
//実行結果
例外が発生しました。
java.lang.ArithmeticException: / by zero
戻り値 = 0
```
このプログラムでは、divメソッドで割り算を行い、その結果を呼び出し元のmainメソッドに返している。0で割り算を行っているので、ArithmeticExceptionの例外をスローして、catchブロックの中の処理が行われていることがプログラムの実行結果から確認できる。

### finallyで必ず実行する処理を記述する方法
finallyブロックの中の処理は、例外の有無に関わらず、最後に必ず実行される。そのため、finallyはファイルを開いた後に確実にcloseメソッドで閉じたいときなどに使用される。
次のテキストファイルを読み込み表示するプログラムで、finallyの使い方を確認してみましょう。
```Main.java
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
public class Main {
 
    public static void main(String[] args) {
        // 1.ファイルのパスを指定する
        File file = new File("c:SampleTest.txt");
 
        // 2.ファイルが存在しない場合に例外が発生するので確認する
        if (!file.exists()) {
            System.out.print("ファイルが存在しません");
            return;
        }
 
        // 3.FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);
 
            int data;
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.最後にファイルを閉じてリソースを開放する
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
}
```
```
//実行結果
SAMURAI
ENGINEER
```
このプログラムのように、finallyブロックの中でcloseメソッドを使うことで確実にファイルを閉じることができる。

### throwで意図的に例外を発生させる方法
throwは意図的に例外をスローして、例外処理を行いたい場合に使う。
```Main.java
public class Main {
 
    public static void main(String[] args) {
        int result;
        result = div(5, 0);
        System.out.println("戻り値 = " + result);
    }
 
    public static int div(int num1, int num2) {
        try {
            if (num2 == 0) {
                throw new ArithmeticException("0で割ったときの例外を発生させる");
            }
            int result = num1 / num2;
 
            return result;
 
        } catch (Exception e) {
            System.out.println("例外が発生しました。");
            System.out.println(e);
 
            return 0;
        }
    }
 
}
```
```
//実行結果
例外が発生しました。
java.lang.ArithmeticException: 0で割ったときの例外を発生させる
戻り値 = 0
```
このプログラムではthrowを使って、0で除算をする場合にArithmeticExceptionの例外をスローしている。

### throwsで呼び出し元で例外処理をする方法
throwsは例外が発生したときにtry-catch文を使ってメソッド内で処理するのではなく、メソッドの呼び出し元で例外処理をしたい場合に使う。

```Main.java
public class Main {
 
    public static void main(String[] args) {
        int result = 0;
        try {
            result = div(5, 0);
        } catch (Exception e) {
            System.out.println("例外が発生しました。");
            System.out.println(e);
        }
 
        System.out.println("戻り値 = " + result);
    }
 
    public static int div(int num1, int num2) throws ArithmeticException {
        int result = num1 / num2;
 
        return result;
    }
 
}
```
```
//実行結果
例外が発生しました。
java.lang.ArithmeticException: / by zero
戻り値 = 0
```
このプログラムではthrowsを使って、呼び出し元のmainメソッドで例外処理を行っている。


### 例外メッセージをカスタマイズする
例外が発生した場合に出力表示するメッセージについて。

これまでのサンプルコードでも、
・System.out.println(e)
・e.printStackTrace()
などを使って例外メッセージを出力表示している。printStackTraceメソッドはよく使用されていますが、例外によってはメッセージが長すぎると感じる場合もある。

例外メッセージは出来ればコンパクトなログとして残したいので、ここではgetMessageメソッドなどを使う方法などをご紹介する。

### printStackTraceメソッドの使い方
以下のサンプルコードを使って例外を発生させ、printStackTraceメソッドで例外メッセージを出力表示させてみよう。

```Main.java
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
public class Main {
 
    public static void main(String[] args) {
        // FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
        File file = new File("c:SampleTest.txt");
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);
 
            int data;
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
 
}
```
```
//実行結果
java.io.FileNotFoundException: c:SampleTest.txt (No such file or directory)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at java.io.FileReader.<init>(FileReader.java:72)
        at Main.main(Main.java:12)
```
このサンプルコードでは、Fileクラスのコンストラクタで指定したファイルが見つからないため、例外FileNotFoundExceptionが発生している。例外処理として、catchブロックでprintStackTraceメソッドを使ってメッセージを出力表示するようにしている。

### getMessageメソッドの使い方
さきほどは、printStackTraceメソッドを使って例外メッセージを出力表示した。でも、メッセージが少々長すぎるので、もう少しコンパクトになるとうれしいですよね。getMessageメソッドを使用すると、メッセージを短くすることができるのでご紹介します。
```Main.java
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
public class Main {
 
    public static void main(String[] args) {
        // FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
        File file = new File("c:SampleTest.txt");
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);
 
            int data;
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
 
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } 
    }
 
}
```
```
//実行結果
c:SampleTest.txt (No such file or directory)
```
このサンプルコードでは、getMessageメソッドを使って例外メッセージを出力表示している。printStackTraceメソッドを使って出力表示されるメッセージの一行目「:」以降をメッセージとして表示。

### getClass、getStackTraceを使う方法
getMessageメソッドを使う方法をご紹介しましたが、これだけでは情報として少ないと感じませんか？せめて、例外の種類、例外発生場所の情報はあった方がいいかもしれません。
例外の種類の情報を記述するためには、getClassメソッドを使う。例外発生場所の情報を記述するためには、getStackTraceメソッドを使う。
```Main.java
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
public class Main {
 
    public static void main(String[] args) {
        // FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
        File file = new File("c:SampleTest.txt");
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);
 
            int data;
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
 
        } catch (IOException e) {
            StackTraceElement[] ste = e.getStackTrace();
            System.out.println(e.getClass().getName() + ": "+ e.getMessage());
            System.out.println("tat "+ ste[ste.length-1]); // 最後の要素の例外発生場所のみ表示
        } 
    }
 
}
```
```
//実行結果
java.io.FileNotFoundException: c:SampleTest.txt (No such file or directory)
        at Main.main(Main.java:12)
```
このサンプルコードでは、getClass().getName()メソッドとgetMessageメソッド、およびgetStackTraceメソッドを使って例外メッセージを出力表示している。

getStackTraceメソッドはprintStackTraceメソッドを使って出力表示される「at」以降の例外発生場所の記述を配列で返す。getStackTraceメソッドで返された配列をStackTraceElement型の配列steに格納している。

配列steの要素の中で、記述したコードの例外発生場所は最後の要素に記述されているので、最後の要素のみ出力表示している。また、getClass().getName()メソッドを使って、該当する例外クラス名を出力表示している。

### Exceptionクラスの種類一覧
![例外.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1162001/23f19afe-ce71-8087-61ba-1425cde713bd.png)

### 独自の例外クラスの作成
例外クラスを継承して、独自の例外クラスを作ることができる。親クラスとなる例外クラスのコンストラクタを継承して使用。親クラスとなる例外クラスのメソッドを使用することも可能。

```Main.java
// 独自の例外クラス
class MyExc extends Exception {
    public MyExc() {
        
    }
    
    public MyExc(String msg) {
        super(msg);
    }
    
    public MyExc(Throwable cause) {
        super(cause);
    }
    
    public MyExc(String msg, Throwable cause) {
        super(msg, cause);
    }
}
 
public class Main {
 
    public static void main(String[] args) {
        int result;
        result = div(5, 0);
        System.out.println("戻り値 = " + result);
    }
 
    public static int div(int num1, int num2) {
        try {
            try {
                if (num2 == 0) {
                    throw new MyExc("0で割ったときの例外を発生させる");
                }
            } catch (MyExc e) {
                throw new MyExc("独自クラスの例外発生", e);
            }
            
            int result = num1 / num2;
            return result;
 
        } catch (MyExc e) {
            e.printStackTrace();
 
            return 0;
        }
    }
 
}
```
```
//実行結果
MyExc: 独自クラスの例外発生
        at Main.div(Main.java:35)
        at Main.main(Main.java:24)
Caused by: MyExc: 0で割ったときの例外を発生させる
        at Main.div(Main.java:32)
        ... 1 more
戻り値 = 0
```
このサンプルコードでは、Exceptionクラスを継承して独自の例外クラスMyExcクラスを作成している。

Exceptionクラスでは、String型の引数を指定するコンストラクタとThrowable型の引数を指定するコンストラクタとString型、Throwable型両方の引数を指定するコンストラクタが定義されている。

MyExcクラスでもこれらのコンストラクタを「super」句を使って継承しています。Mainクラスを実行すると、String型文字列"0で割ったときの例外を発生させる"で例外を投げ、catchブロックで一旦例外を受け取る。

さらに、一旦受け取った例外をString文字列”独自クラスの例外発生”とThrowable型変数eで例外として投げ、catchブロックで受け取っている。

### まとめ
例外処理が行われていないと、実行時エラーが発生したときにそこでプログラムが終了してしまうので注意すること。


### 参考サイト
https://www.sejuku.net/blog/26344