例外処理はJavaにとっても大変重要な処理の一つです。今回はそんな例外処理について少し掘り下げて解説していきます!
エラーについて
まずこの”例外”を日本語で完結に説明すると、想定される以外の事案が発生した場合を指します。
プログラミングの世界でも例外は当然存在しますが、その代表的なものとして「エラー」があります。
以下ではエラーの種類について簡単に解説しています。
コンパイルエラー
文法にミスがあったりタイプミスがあるときに出てくるエラーです。Javaに限らずプログラミングでは間違いなく最も多いエラーではないでしょうか?
またコンパイルエラーは代入ミスや型違いでの指定などでも起こります。
要はコンパイル(コンピューター言語への変換)が出来ない状態を表します。
実行時エラー
問題なく(と言ってもエラーなので問題はあるのですが)実行は出来ているけど何らかの異常で処理落ちするなどの現象全般を指します。
無限ループによるスタックオーバーフローやプロセスクラッシュもこれに該当します。
論理エラー
実行時エラーも引き起こさずコンパイルも無事完了しきちんと動作しているかのように見えて、実際には想定外の動作を引き起こしてしまうなどの状態を表します。
このエラーは開発者が最も気付き難いエラーと言ってもいいでしょう。
気付き難い理由として「10以上かつ10未満の数値を抽出する」など言葉として意味不明なものであっても、プログラムの性質上、作業工程に致命的欠陥が無ければコンパイルも実行も可能であり、エラー表示もされないところでしょうか。
比較演算子を使った比較でnew演算子を使用して作成したオブジェクト同士を比較する場合もこれに該当します。
例外(Exception)について
プログラムで実行処理を行う場合に、予期しない事象で処理が中断されたりエラーが吐き出されることがあります。
先ほどエラーについての解説をしましたが、ここでは例外について触れてみたいと思います。
なおここで触れる例外というのは、上記でいうところの”実行時エラー”に当たる部分と思ってください。
Error
無限ループによるスタックオーバーフローやプロセスクラッシュに代表される、重大かつ回復不能な事象を指します。JRE自体が強制終了することもしばしばあり、そういった場合にはエラーの原因を特定できないことも少なくありません。
また強制終了にはこれらの他に、どこにもエラーが無い場合でもメモリ不足が引き起こす場合もあります。
RuntimeException
実行時(Runtime)に起きた例外(Exception)を指します。実行自体は出来ているため、プログラムの仕様や状況によっては特に気にしなくてもいい場合もあります。
しかしながら例外であることには変わりありません。
慣れていないうちは常にプログラムの不具合を疑っている方が安全な為、修正を練習する上でもきちんと対応することが好まれます。
RuntimeExceptionの具体的な例としては以下の様な原因が挙げられます。
- NumberFormatException – parseIntやtoStringメソッドを使用する際に文字列や数値になっていなかった場合
- NullPointerException – インスタンスを呼び出す際に存在しないインスタンスを呼び出してしまった場合
Exception
上記で解説している以外の例外全般を指します。
Exceptionは上記二つとは違い、例外処理を行わなければコンパイル出来ない仕様となっていますので、例外処理の記述を省略することが出来ません。
Exceptionの代表的な例としては”FileNotFoundException”が挙げられます。
例外処理の方法
ここまではエラーとその他の例外について触れてきましたが、次からはそれらの処理方法について解説していきます。
まず例外処理には以下の二つの方法があります。
- throwsを使う方法
- try-catchを使う方法
これら二つの方法について詳しく説明します。
throwsを使う方法
throwsを使う場合、そのメソッド内で例外が発生した場合にはメソッド内でCatchせず呼び出し元に例外を投げて処理します。
例えばFileReaderクラスを使う場合にはどのような例外が発生し得るのか容易に想像できるため、throwsで予め例外を指定することが出来ます。
なお似たようなものとして”throw”がありますが、こちらはthrowsとは真逆の性質を持っている別物となっていますので混同しないようにしましょう。
では実際にthrowsを使ってコードを記述してみましょう。
import java.io.FileNotFoundException; import java.io.FileReader; public class Main { public static void main(String[] args) { String fileName = "sample.txt"; try { read(fileName); } catch (FileNotFoundException e) { System.out.println("ファイルが存在しませんでした。"); } } public static void read(String fileName) throws FileNotFoundException { FileReader r = new FileReader("sample.txt"); System.out.println("ファイルを読み込みました"); } }
実行結果
ファイルが存在しませんでした。
このコードではFileReaderクラスを使ってファイルを呼び出していますが、あえて実際には存在しないファイルの呼び出しをしています。
throwsによって呼び出し元クラスのmainメソッドに例外処理を投げた後、mainメソッドに記述されているcatch内の処理が実行されていることが分かります。
try-catchを使う方法
try-catchも同じ例外処理ですが、こちらはかなり自由度の高い例外処理と理解できます。
一般的な使い分けとしては、自作したオリジナルクラスの例外に対する処理をさせたい場合などで使う場合が多いようです。
ではこちらの処理も実際のコードで見てみましょう。
public class Main { public static void main(String[] args) { int sampleInt; sampleInt = arr(3, 0); System.out.println("戻り値 = " + sampleInt); } public static int arr(int num1, int num2) { try { int quotient = num1 / num2; return quotient; } catch (ArithmeticException e) { System.out.println("例外が発生しました。"); System.out.println(e); return 0; } } }
実行結果
例外が発生しました。 java.lang.ArithmeticException: / by zero 戻り値 = 0
このコードではオリジナルメソッドで割り算を行い、結果をmainメソッドに投げるという単純なプログラムとなっています。
オリジナルメソッド内では0で割り算を行っているため、発生した例外がcatchに投げられcatchブロックの中の処理が反映されていることが分かります。
Finally処理
Finallyは、例外の有無に関わらず必ず処理させたい場合に使います。
import java.io.FileNotFoundException; import java.io.FileReader; public class Main { public static void main(String[] args) { String fileName = "sample.txt"; try { read(fileName); } catch (FileNotFoundException e) { System.out.println("ファイルが存在しませんでした。"); } finally { System.out.println("絶対に実行させる処理を記述できます。"); } } public static void read(String fileName) throws FileNotFoundException { FileReader r = new FileReader("sample.txt"); System.out.println("ファイルを読み込みました"); } }
実行結果
ファイルが存在しませんでした。 絶対に実行させる処理を記述できます。
上で説明したコードに”finally”を足しただけのシンプルなコードですが、例外の有無に関わらずfinallyブロック内の処理が実行されていることが分かります。
まとめ
今回は例外と例外処理について解説してきました。
最初はcatchに戸惑うかもしれませんが、throwsとtry-catchの使い分けに注意しながら色々と試してみて例外処理にも慣れていきましょう。