Java Programming

【Java】例外エラーの基礎

MediamodifierによるPixabayからの画像

こんにちわ。しろです。

プログラミングの経験がある方は、例外エラーという言葉を聞いたことがあると思います。

例外エラーは、基礎を理解することが大変で、プログラミングをはじめたばかりの方が苦戦する関門だと思います。
※僕も最初は苦労しました。

本記事では、例外エラーについて、なるべく分かり易くまとめた内容となっているので、苦戦している方は、ぜひ参考にして下さい。

例外エラーとは

一言でいうと、「想定外のエラー」のことです。

それ以外のエラーを「想定内のエラー」といいます。

想定内のエラーとは

例えば、オンラインのショッピングサイトにログインするシーンを想像して下さい。

まずはじめに、ログイン画面を開いて、そこでログインIDとパスワードを入力しますね。

その際、タイプミスで誤った入力をしてしまうと通常は、

ログインID、もしくはパスワードが誤っています。

という感じのエラーメッセージが表示され、「正しい内容で、もう一度入力してよー」と訂正を促されます。

これは、アプリケーションが予め、利用者の入力ミスを想定して設けた仕様であり、そこで発生したエラーを「想定内のエラー」といいます。

想定外のエラー(例外エラー)とは

先ほどのログイン画面の延長で例えますね。

ログインID、パスワードを入力し、ログインボタンを押した際、アプリケーションが次のような状況下にある場合、例外エラーが発生しえます。

  • 入力内容を照合するための情報を管理しているデータベースがダウンしている。
  • サーバーにアクセスが集中して、メモリ等のリソース不足で処理できない。
  • アプリケーション側の処理にバグがある。

その結果、通常は、

システムエラーが発生しました。

という感じのエラーメッセージが表示され、利用者側では対処できません。

こんな感じで、利用者側で対処できないエラー、もしくはアプリケーションが想定していないエラーが「想定外のエラー」です。
※上記の例えは、想定外のエラーが発生た場合に、メッセージを表示するように作っているわけです。つまり、想定外の想定をしていることになります。ややこしいですね....

例外エラーの種類

Javaの例外エラーは、次の2つの種類に分かれます。

  • チェック例外(検査例外ともいいます。)
  • 非チェック例外(非検査例外ともいいます。)

チェック例外(検査例外)とは

チェック例外とは、アプリケーションの通常処理の流れを変えてしまう例外エラーのことです。

先ほどの例では、「入力内容を照合するための情報を管理しているデータベースがダウンしている。」が該当し、アプリケーションを取り巻く環境に影響されるなどで発生します。

また、チェック例外は、プログラムで必ず対処するよう求められます。対処していない場合はコンパイルエラーになります。

コンパイラに「プログラム側で必ずチェックしてよ」と求められるので、チェック例外というわけですね。

※ちなみに、他プログラミング言語、例えばC#、VB.NETなどにはチェック例外の概念はありません。

非チェック例外(非検索例外)とは

非チェック例外とは、主に次に該当するものを指します。

  • アプリケーションで処理すべきではない重大なエラー
  • プログラムのバグに起因した例外エラー

それぞれ、先ほどの例の「2」、「3」に該当します。

例外クラスの種類

まずは代表的な例外クラスの継承関係をみてみましょう。

このクラス群を種類分けすると以下のようになります。

クラス名種類説明
Throwableチェック例外Error、RuntimeException以外の派生クラスもチェック例外となる。
Error非チェック例外派生クラスも非チェック例外となる。
Exceptionチェック例外RutimeException以外の派生クラスもチェック例外となる。
RuntimeException非チェック例外派生クラスも非チェック例外となる。

次に、各例外クラスの概要を説明します。

Throwable(チェック例外)

すべての例外クラスの親クラスです。

つまり、どんな例外クラスであっても、必ずこのクラスが最上位の親クラスになります。

Error(非チェック例外)

アプリケーション側が処理すべきではない、通常起こりえない重大な事態が発生した場合に利用する例外クラスです。

例えば、無限ループでスタックを埋め尽くした際に発生するStackOverflowErrorという例外クラスがあり、これはErrorの派生クラスです。

StackOverflowErrorをアプリケーション側で処理する必要はないですね。

Errorについては、知識として、頭の片隅にとどめておく程度で良いと思います。

Throwableの直接の派生クラスは、Error、Exception。

Errorはアプリケーション側で考慮する必要がない。

ということは、アプリケーション側で、すべての例外エラーをまとめて処理したい場合は、Exceptionを補足して処理すれば良い、ということですね。

※派生クラスは親クラス型で補足できるため。

Exception(チェック例外)

Exceptionと、その派生クラス(※1)は、アプリケーションで必ず処理する必要がある例外クラスです。

例えば、「ファイルを開いて、文字を書き込む」場合に、すでにファイルが開かれていると発生するIOExceptionが派生クラスの一つです。

プログラムで対処しないとコンパイルエラーになります。

(※1)RuntimeExceptionとその派生クラスは除く。

RuntimeExeption(非チェック例外)

このクラスと、その派生クラスは、アプリケーション側で処理しても、しなくても良い例外クラスです。

例外処理を伝播させたいシーンがあるのでそういったときに良く使います。

個人的には、RuntimeExeptionから派生した、IllegalArgumentException(引数不正の場合にthrowする例外クラス)をよく利用します。

例外処理の方法

例外処理は次の2つの方法があります。

  • catchして処理する。
  • catchはせずに、呼び出しもとに委ねる。

catchして処理する

発生する可能性のある例外エラーをcatch(補足)し、例外処理を行う方法です。

構文

tryブロック

tryブロックには、例外エラーが発生しえる処理を含めた内容を実装します。

tryブロック内で例外エラーが発生した場合は、即座にcatchブロックに流れます。

catchブロック

catchブロックは、throwされた例外クラスの型と一致するか、親クラスの型と一致したcatchブロックで処理されます。

また、例外クラスの型ごとに異なる処理を行いたい場合は、型ごとにcatchブロックを複数実装します。

前述でも述べたように、アプリケーションが処理すべき例外エラーをまとめてcatchしたい場合は、その上位クラスであるExceptionでcatchすればOKです。catch (Exception e)とかきます。

finallyブロック

finallyブロックは、例外エラーの発生有無にかかわらず、tryブロック、catchブロックのあとに実行したい処理を実装します。

例えばアプリケーションでファイルをオープンした場合、利用後はメモリから解放するために、必ずクローズする必要があります。

この場合、クローズ処理をfinallyブロックに実装しておけば、必ずクローズを通過するので、オープンしたままの状態を回避できます。

また、例外処理はcatch(キャッチ)するか、上位にthrow(スロー)するかのいずれかです。

実装例

次のコードは、以下のように動作します。

  • 引数の要素数が1の場合、引数の内容を出力する。
  • 引数の要素数が1ではない場合、例外エラーが発生する。
引数を1つ指定して実行した場合

引数を1つ指定して実行した場合は、①(チェックOK) → ② → ③という流れを経て、次の内容が出力されます。

引数[0]の内容:あいうえお
finallyの処理を通過.

引数を指定せずに実行した場合

引数を指定せずに実行した場合は、①(チェックNG) → ★ → ③という流れを経て、次の内容が出力されます。
※①でチェックNGとなり、例外エラーがthrowされ、②の処理を飛ばして、★に流れます。

引数は、必ず1つ指定してください。
finallyの処理を通過.

catchはせずに、呼び出しもとに例外処理をゆだねる

例外エラーをcatchせずに、例外処理を呼び出しもとに委ねることもできます。

その場合は、「僕からはこういう例外がスローされるかもですよ」とメソッドの宣言部に記述する必要があります。throws 例外クラス型というふうに記述します。

また、複数の例外をthrowする可能性がある場合は、","(半角カンマ)区切りで、throws 例外クラス型①, 例外クラス型②というふうに記述します。

実装例

次のコードは、先ほどのコードからcatchブロックを除去し、throws利用に書き換えたものです。

※public static void main(String[] args) throws Exception

引数を指定せずに実行した場合

引数を指定せずに実行した場合は、①(チェックNG) → ③という流れを経て、次の内容が出力されます。

ちなみにこのコードの場合、呼び出しもとがJVM側になるので、例外処理はそこで行っています。

finallyの処理を通過.
Exception in thread "main" java.lang.Exception: 引数は、必ず1つ指定してください。
at exception/com.shirobetween.Main.main(Main.java:17)

補足

tryブロックを実装した場合、catchブロック、finallyブロックのいずれかを実装する必要があります。

いずれも実装していない場合、コンパイルエラーとなってしまいます。

try-catchの例(finallyなしの例)

try-finally(catchなしの例)

以上、例外処理の基礎でした。

最後まで読んで頂き有難うございます。

-Java, Programming
-, , , , , ,

© 2020 しろブログ