名古屋のITエンジニア向け研修会社。目次Toggleなぜ、例外の理解が重要なのか、その理由1.try~catch文2.finally句の意義3.複数例外のキャッチ4.try-with-resourcesを使うとリソースを閉じるコードを書かなくて良い5.例外クラスの体系6.throwsキーワード7.例外を呼び出し元に投げて処理を任せるなぜ、例外の理解が重要なのか、その理由この記事では、弊社の新人エンジニア研修の参考にJavaを解説します。前回はインタフェースについて解説しました。今回は例外処理について解説します。例外処理も継承を巧みに使った仕組みの一例ですので復習にもなると思います。この短い新人エンジニア研修の中でも例外がいくつか出てきました。3.演算子のところで紹介したArithmeticException(標準API)6.配列の作成と使用のところで紹介したArrayIndexOutOfBoundsException(標準API)9.インスタンスの活用のところで紹介したNullPointerException(標準API)10.継承(拡張)のところで紹介したClassCastException(標準API)等がありましたね。例外とは、人間に向けたプログラムからのトラブル報告書です。このままでは処理を継続できないという報告です。あなたがお使いのゲームアプリが突然意味不明なメッセージを表示して落ちてしまったらどうでしょうか?その上、何時間もかけて蓄積したセーブデータが残っていないとしたら。エラーが発生しても続行するか、重要なデータを保存してから適切に終了するようにプログラミングしないといけません。そのような処理を例外処理といいます。例外が存在しなかった時代のエラー処理は大変でした。深い階層で起こったエラーを一生懸命バケツリレーで呼び出し元の処理に伝えていたのです。メソッドが返せる値は一つだけですから、大した情報も伝えられませんでした。エラーが発生した時にだけ投げる例外という仕組みができたおかげで、エラー時の処理が画期的に改善されたのです。例外処理の構文を以下に示します。{最後に必ず行う処理}では、順番に解説していきます。以下のExample01はtry~catch文を解説するプログラムです。zeroこのプログラムを終了しますこのように、例外が発生する恐れのある個所をtryブロックで囲みます。英語の【try】には元来、「上手くいくかどうか分からないが試してみる」という意味があります。catchブロックには例外が発生した時の処理を書きます。例外をキャッチするという文字通りの意味です。catchの後ろの()の中には、あたかもメソッドの仮引数のように例外の型と変数を書きます。変数名は何でも良いですが、【error】の頭文字eや【exception】のexを使うのが慣例となっています。変数eの中身をコンソール出力すると例外の内容(原因)が分かります。例外が発生したらcatchブロックの処理が実行されるのですが、そうするとIF文との違いに迷う新人エンジニアの方がいます。一般的なガイドラインとして、プログラムの制御フローの分岐や条件に基づく処理は、if文を使用し、予期しないエラーへの対応やエラーハンドリングは、例外処理を使用することが推奨されます。例外が発生した時の処理といっても何か有効なトラブル対応処理が書けるわけではありません。多い処理は人間にトラブルの内容を知らせてログに記録することです。次にありえるのはデータベースやファイルをちゃんと閉じる処理です。例外処理がなければここでプログラムが中断するところですが、今回は中断することなく最後まで終えることができています。エラー処理が主要なロジックから分離されている点にも注目してください。tryブロックで囲まれた主要なロジックとcatchブロックで囲まれたエラー処理のコードが分離されているため、コードの主要な目的が理解しやすくなっています。2.finally句の意義finallyブロックにかかれた処理は例外発生の有無を問わず必ず実行されます。ファイルのクローズやデータベースのクローズに使うというのがfinallyブロックの典型的な利用例です。以下のExample02はfinally句を説明するプログラムです。"2byte"このプログラムを終了します例外が発生すると処理は一気にcatchブロックまで飛んでしまうため、8行目の処理が実行されません。こんな時はどうしたらよいのでしょうか?例えば以下のように同じ処理を2回記述するコードは冗長です。}}以下のようにfinallyブロックを追加すれば繰り返しの記述を避けることができます。zero例外発生の有無にかかわらず実行したい処理ですこのプログラムを終了しますfinallyブロックにかかれた処理は例外発生の有無を問わず必ず実行されます。当社の新人エンジニア研修の範囲でのfinallyの活用場面としては、データベース処理において例外が発生した場合に、データベースとの接続をクローズするという使い方があります。この点は機会があれば「Javaからデータベースのデータを活用する」というテーマでご紹介したいと思います。それまではfinally句の存在意義は理解できないと思います。3.複数例外のキャッチ起こりうる複数の例外の場合に分けて、以下のように例外処理を書き分けることができます。3このプログラムを終了しますこのようにtry~catch文は最初に発生した例外を捕捉します。4.try-with-resourcesを使うとリソースを閉じるコードを書かなくて良い以前、繰り返しのところで以下のように数え上げ回数をキーボード入力するプログラムを作成しました。この時に、try-with-resourcesという構文を使っていました。try-with-resourcesは、ファイルやデータベース、ネットワーク接続などのシステムリソースを使用する際に非常に有用です。これらのリソースは、使用後に閉じる必要がありますが、従来のtry-catch-finallyブロックを使用するとコードが冗長になります。try-with-resourcesを使用すると、リソースが自動で閉じられるため、プログラマはリソースを閉じるコードを書く必要がありません。}}try-with-resourcesでは、try文の直後に括弧を使用して、クローズ可能なリソースを宣言します。(このリソースはAutoCloseableまたはCloseableインターフェースを実装している必要があります)tryブロックが終了したら、自動的にclose()メソッドが呼び出され、リソースが安全に閉じられます。try-with-resourcesを使わないと以下のようにfinally句が必要になり冗長なコードになります。コードが簡略化されていたのがお分かりになると思います。例によってIDEを使いArithmeticExceptionクラスから、スーパークラスをさかのぼって行きましょう。ArithmeticExceptionクラスの40行目をみてください。{(中略)Java17標準APIとなっていてRuntimeExceptionクラスのサブクラスであることが分かりました。次にRuntimeExceptionクラスの43行目を見てスーパークラスを探します。{(中略)Java17標準APIとなっています。このExceptionがすべての例外クラスの親玉(?)です。Exceptionクラスの定義を45行目に見ると以下のようにThrowableクラスのサブクラスとして宣言されています。標準API【Throwable:投げられる】という意味のクラスですね。このThrowableクラス(標準API)を調べると以下の記述があります。Throwableクラスは、Java言語のすべてのエラーと例外のスーパー・クラスです。Throwableのスーパークラスは例のObjectクラスです。Throwableの直系の既知のサブクラスについて以下の記述があります。ExceptionExceptionクラスには兄弟分のErrorクラスがあるということですね。ここまでを図示すると下図のようになります。この中でまず抑えるべきはその例外クラスが、RuntimeExceptionクラスのサブクラスであるか、それ以外のExceptionクラスのサブクラスかという区別です。RuntimeExceptionクラスのサブクラスであれば、try~catch文は書かなくても良いことになっているからです。これまでも配列を使ったときにtry~catch文を書かなくても良かったですね。例外が起きる可能性のある全てのコードにtry~catch文を強制していたらJavaプログラムは例外処理で埋め尽くされてしまうからです。try~catch文の記述が任意である例外を非チェック例外(非検査例外)といいます。※あるいは実行時例外ともいいます。RuntimeExceptionの直訳です。ちなみに、今までご紹介してきたArithmeticException、ArrayIndexOutOfBoundsException、NullPointerException、ClassCastException、NumberFormatExceptionはすべてRuntimeExceptionクラスのサブクラスであり、非チェック例外です。Errorクラスが発生する可能性のあるコードもtry~catch文で囲む必要はありません。Errorクラス(標準API)の説明を見るとErrorはThrowableのサブクラスで、通常のアプリケーションであればキャッチすべきではない重大な問題を示します。したエラーの大部分は異常な状態です。Java仮想マシンが壊れているとかメモリ不足等の事態です。いかんともしがたいわけです。過去の記事の中では、1回目のところでStackOverflowErrorというのを見ましたがあれは、ErrorクラスのサブクラスのVirtualMachineErrorクラスのサブクラスです。Exceptionクラスのサブクラスであって、RuntimeExceptionクラスのサブクラスでないクラスは、すべてチェック例外(検査例外)ですのでtry~catch文で囲む必要があります。チェック例外として当社の研修内容に関連するものとしては、例えばSQLExceptionがあります。これはデータベースに関連した例外ですので、そこでお話ししたいと思います。上記の例外クラスの体系を見て明らかなように以下のサンプルプログラムは意図した通りの処理を行います。3このプログラムを終了しますこのように全ての例外クラスはそのスーパークラスであるExceptionクラス型で捕捉できるようになっています。プログラマの意図が不明瞭になりがちですので手抜きで記述することは避けた方が良いでしょう。※IDEからも複数のキャッチ句に置換しうることを提案されると思います。Exceptionクラス型は、具体的な例外を全てcatchして、それでも発生するかもしれない例外を補足する時に使います。ここまでは例外をキャッチする側の処理を見てきましたので、逆に例外をスローする側の処理がどうなっているのかを探検してみましょう。6.throwsキーワード例外を呼び出し元に投げて処理を任せることができます。IntegerクラスのparseInt()メソッドの定義の先頭部分である620行目以降をご覧ください。標準APIまず、メソッド名()の後ろの記述をご覧下さい。NumberFormatExceptionこのparseInt()メソッドはNumberFormatExceptionを投げる可能性のあることを示しています。nullを受け取ったらNumberFormatExceptionを投げるということを541行目で示しています。メソッドがスローする可能性がある例外を宣言することで、呼び出し元で例外をキャッチする必要があることを明示的に示し、プログラムの可読性と保守性を高めているのです。複数の例外を投げる可能性がある場合は、カンマで区切って複数宣言することもできます。NumberFormatException("null");throwとthrowsで“s”が付くか付かないかが分かりにいかも知れませんね。強引ですが、1実際に1つ1つの例外を投げる際には動詞のthrow2このメソッドは(複数の)例外を投げる可能性のあるメソッドであるというときは名詞の複数形でthrowsと覚えていただくと比較的スムーズかと思います。chap13.Example09.main(Example09.java:6)確かにNumberFormatExceptionが発生しました。この結果をスタックトレース情報というのだとこのテキストの1回目にお話しました。1行目はどんな例外が発生したかというのを表し、2行目以降でその例外の発生源とその例外がどのように伝播していったかを示すのでした。その順番は、"スタック"トレースという名前のとおり、メソッドの呼び出し順を発生源から順に上に積んでいきます。スタックトレース情報は上から下に見ていって、自作のクラスの自作のメソッドが怪しいのでそこを重点的に調べればOKです。()の中の数字は例外発生の原因箇所の行番号です。クリックするとソースコードに飛べます。(ただし、かならずしもその行が例外発生の原因とは限りません。その周辺をチェックして下さい)スタックトレースの見方7.例外を呼び出し元に投げて処理を任せるこの仕組を使って自作のクラスでも例外処理を呼び出し元に投げる事ができます。以下のExample10はサンプルプログラムは例外を呼び出し元に投げて処理を任せる例です。コンストラクタで例外を発生させて呼び出し元のメインメソッドに投げています。受け取ったメインメソッドも例外を投げていますが、受取り手はJVMですので結局はスタックトレース情報を表示して終わっています。chap13.Example10.main(Example10.java:6)もちろんこのような無責任なプログラムは例示であって推奨しているわけではありません。上記のプログラムでは例外の生成とthrowを1行で済ませていますのではじめは少し理解が困難かもしれません。2行で書くと以下のようになります。e;}その他にオリジナルの例外クラスを作るというテーマでリンクに記事があります。余裕のある方は一読下さい。例外発生は握りつぶしてはいけない。最低限logに出力すべきだが、研修中はコンソールに出力するまとめができたら、アウトプットとして演習問題にチャレンジしましょう。セイコンサルティンググループ以下のプログラムには、題意を明確にするためにあえて無意味な変数名を使用しています。セイコンサルティンググループ以上、今回は「例外処理で想定外の事態に強いシステムにする」方法について見てきました。例外処理を使うことでプログラムはトラブルの発生をユーザーに伝えることができ、途中終了を避けることができるのでした。継承の仕組みを上手く応用しているのも興味深いところでしたね。実際にお客様の依頼で開発するアプリでは、詳細なエラー情報は画面に表示しないことが多いのです。深刻なエラーが起こった場合はなおさらです。知識が不十分なお客様をいたずらに不安にさせることになりますし、下手をすれば責任問題に発展し、会社同士のもめ事にもなりかねないからです。いう場合はいつ、何が起こったのかを詳細にログに出力し、画面には、ただ「システム管理者に連絡して下さい」とだけ表示するようにします。非チェック例外に対しても例外処理を何もしなくて良い、というわけでは決して無い、ということも、分かっておいてください。次回は、「ArrayListでもっと配列を便利に使いこなす」です。ArrayListを一言で言うと便利なメソッドを持った伸縮自在の配列です。現在では配列よりもよく使われています。いうことで、次回を楽しみに待っていてください。最後までお読みいただきありがとうございます。