この記事では、当社 の新人エンジニア研修の参考にJava8を解説します。
前回はインターフェースについて解説しました。
今回は例外処理について解説します。
例外処理も継承を巧みに使った仕組みの一例ですので復習にもなると思います。
この短い連載の中でも例外がいくつか出てきました。
3.演算子のところで紹介したArithmeticException
6.配列の作成と使用のところで紹介したArrayIndexOutOfBoundsException
9.インスタンスの活用のところで紹介したNullPointerException
10.継承(拡張)のところで紹介したClassCastException
12.抽象クラスのところで紹介したNumberFormatException
等がありましたね。
例外とは、人間に向けたプログラムからのトラブル報告書です。
このままでは処理を継続できないという報告書です。
したがって例外が発生しても処理を継続するようにプログラミングすることが重要になってきます。
これを例外処理といいます。
例外処理の構文を以下に示します。
try { 例外が投げられる可能性のある処理 } catch(例外の型1 変数名1) { 例外の型1の例外が投げられたときの処理 } catch(例外の型2 変数名2) { 例外の型2の例外が投げられたときの処理 } finally { 最後に必ず行う処理 } |
では、順番に解説していきます。
1.try~catch文
例外が発生したとき、その例外をキャッチして処理を継続することができます。
その際に使うのがtry~catch文です。
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package Chap14; public class Example1 { public static void main(String[] args) { try { System.out.println(5 / 0); } catch (ArithmeticException e) { System.out.println("例外が発生しました"); System.out.println(e); } System.out.println("このプログラムを終了します"); } } |
<結果>
例外が発生しました java.lang.ArithmeticException: / by zero 終了します |
このように、例外が発生する恐れのある個所をtryブロックで囲みます。
catchの後ろの()の中には、あたかもメソッドの仮引数のように例外の型と変数を書きます。
catchブロックには例外が発生した時の処理を書きます。
なお、時々誤解する人がいますが、例外が発生した時の処理といっても何か有効なトラブル対応処理が書けるわけではありません。
せいぜいが人間にトラブルの内容を知らせることが関の山です。
ここで変数eの中身を見ると例外の内容(原因)が分かります。
また、例外処理がなければここでプログラムが中断するところですが、今回は中断することなく最後まで終えることができています。
2.finallyの意義
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package Chap14; public class Example2 { public static void main(String[] args) { try { System.out.println(5 / 0); System.out.println("例外発生の有無にかかわらず実行したい処理です"); } catch (ArithmeticException e) { System.out.println(e); } System.out.println("このプログラムを終了します"); } } |
例外が発生すると処理は一気にcatchブロックまで飛んでしまうため、8行目の処理が実行されません。
こんな時はどうしたらよいのでしょうか?
例えば以下のように同じ処理を2回記述するコードは冗長です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Chap14; public class Example3 { public static void main(String[] args) { try { System.out.println(5 / 1); System.out.println("例外発生の有無にかかわらず実行したい処理です"); } catch (ArithmeticException e) { System.out.println("例外発生の有無にかかわらず実行したい処理です"); System.out.println(e); } System.out.println("このプログラムを終了します"); } } |
そんな時は、finallyブロックを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Chap14; public class Example3 { public static void main(String[] args) { try { System.out.println(5 / 0); } catch (ArithmeticException e) { System.out.println(e); } finally{ System.out.println("例外発生の有無にかかわらず実行したい処理です"); } System.out.println("このプログラムを終了します"); } } |
finallyブロックにかかれた処理は例外発生の有無を問わず必ず実行されます。
finallyの活用場面としては、データベース処理において例外が発生した場合に、データベースとの接続をクローズするという使い方が想定されます。
ただし、この点に関してはtry-with-resourcesを使うことが推奨されており、この点は機会があればデータベースのところで紹介したいと思います。
3.複数例外のキャッチ
起こりうる複数の例外の場合に分けて、以下のように例外処理を書き分けることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package Chap14; public class Example2 { public static void main(String[] args) { try { int[] numbers = {1,2,3}; numbers[3] = 4; System.out.println(5 / 0); } catch (ArithmeticException e) { System.out.println(e); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(e); } System.out.println("このプログラムを終了します"); } } |
<結果>
java.lang.ArrayIndexOutOfBoundsException: 3 このプログラムを終了します |
なお、Java 7からmulti-catch(マルチキャッチ)という構文が追加されていますので興味のある方は調べてみてください。
4.例外クラスの体系
例によってIDEを使いArithmeticExceptionクラスのスーパークラスをさかのぼって行きましょう。
public class ArithmeticException extends RuntimeException
となっていてRuntimeExceptionクラスのサブクラスであることが分かりました。。
では次にRuntimeExceptionクラスのスーパークラスを探します。
public class RuntimeException extends Exception
となっています。
このExceptionがすべての例外クラスの親玉(?)です。
さらにさかのぼると
public class Exception extends Throwable
となり、Throwable(投げられる)という意味のクラスがあります。
このThrowableクラスをAPIで調べると以下の記述があります。
Throwable
クラスは、Java言語のすべてのエラーと例外のスーパー・クラスです。
そして、Throwableのスーパークラスは例のObjectクラスです。
直系の既知のサブクラスについて以下の記述があります。
クラスThrowable
- java.lang.Object
- java.lang.Throwable
- すべての実装されたインタフェース:
- Serializable
Exceptionクラスには兄弟分のErrorクラスがあるということですね。
つまり、ここまでを図示すると以下のようになります。
この中でまず抑えるべきはRuntimeExceptionクラスのサブクラスであるか、それ以外のExceptionクラスのサブクラスかという区別です。
なぜなら、RuntimeExceptionクラスのサブクラスであれば、try~catch文は書かなくても良いことになっているからです。
なぜなら、もし、try~catch文を強制していたらJavaプログラムは例外処理で埋め尽くされてしまうからです。
try~catch文の記述が任意である例外を非チェック例外といいます。
※あるいは実行時例外といいます。RuntimeExceptionの直訳ですが、意味が伝わりにくいのでこの記事では、非チェック例外の表現で通します。
ちなみに、今までご紹介してきたArithmeticException、ArrayIndexOutOfBoundsException、NullPointerException、ClassCastException、NumberFormatExceptionはすべてRuntimeExceptionクラスのサブクラスであり、非チェック例外です。
なお、Errorクラスが発生する可能性のあるコードもtry~catch文で囲む必要はありません。
Errorクラスの説明をAPIで見ると
Error
はThrowable
のサブクラスで、通常のアプリケーションであればキャッチすべきではない重大な問題を示します。そうしたエラーの大部分は異常な状態です。
とあります。
例えば、Java仮想マシンが壊れているとかの事態です。いかんともしがたいわけです。
過去の記事の中では、8.クラスメソッドのところで
StackOverflowError
というのを見ましたがあれは、ErrorクラスのサブクラスのVirtualMachineErrorクラスのサブクラスです。
Exceptionクラスのサブクラスであって、RuntimeExceptionクラスのサブクラスでないクラスは、すべてチェック例外ですのでtry~catch文で囲む必要があります。
チェック例外として当社の研修内容に関連するものとしては、SQLExceptionがあります。
ただこれは、データベースに関連した例外ですので、そこでお話ししたいと思います。
上記の例外クラスの体系を見て明らかなように以下のサンプルプログラムは意図した通りの処理を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package Chap14; public class Example5 { public static void main(String[] args) { try { int[] numbers = {1, 2, 3}; numbers[3] = 4; System.out.println(5 / 0); } catch (Exception e) { System.out.println(e); } System.out.println("このプログラムを終了します"); } } |
<結果>
java.lang.ArrayIndexOutOfBoundsException: 3 このプログラムを終了します |
ただし、プログラマの意図が不明瞭になりがちですので避けた方が良いでしょう。
4.オリジナルの非チェック例外を作成する
Exceptionクラスまたはそのサブクラスを継承したオリジナルの例外を作成することができます。
11.カプセル化のところで使ったカジノゲームの残高の例で説明をしてみます。
以下のサンプルプログラムを見てください。
非チェック例外のオリジナル例外クラスを作成してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package Chap14; public class Example6 { public static void main(String[] args) { Player p1 = new Player(); p1.withdraw(200); p1.withdraw(1000); } } class Player { private int balance = 1000; void withdraw(int amount) throws OriginalException { if ((balance - amount) < 0) { OriginalException e = new OriginalException("残高不足です"); throw e; } else { balance -= amount; System.out.println("現在の残高です:" + balance); } } } class OriginalException extends RuntimeException{ public OriginalException(String message) { super(message); } } |
<実行結果>
現在の残高です:800 |
まず、ご覧いただきたいのは26行目以降です。
class OriginalException extends RuntimeException
と、RuntimeExceptionクラスのサブクラスでOriginalExceptionクラスを宣言しています。
コンストラクタは、
super(message);
となっており、スーパークラス(RuntimeException)のコンストラクタをそのまま使っています。
そして、17行目ではOriginalExceptionクラスのインスタンスを作成しています。
OriginalException e = new OriginalException(“残高不足です”);
また、18行目ではそのインスタンスをthrow(投げる)ということをしています。
throw e;
この投げられた例外は呼び出し元のメインメソッドに投げられます。
実行結果はこの例外のたどった経路を示しています。
5.オリジナルのチェック例外を作成する
OriginalExceptionクラスは非チェック例外でした。
では、チェック例外に変えてみるとどのようなことが起こるでしょうか?
といっても以下のようにExceptionクラスを継承するだけです。
class OriginalException extends Exception
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package Chap14; public class Example6 { public static void main(String[] args) { Player p1 = new Player(); p1.withdraw(200); p1.withdraw(1000); } } class Player { private int balance = 1000; void withdraw(int amount) throws OriginalException { if ((balance - amount) < 0) { OriginalException e = new OriginalException("残高不足です"); throw e; } else { balance -= amount; System.out.println("現在の残高です:" + balance); } } } class OriginalException extends Exception { public OriginalException(String message) { super(message); } } |
<結果>
現在の残高です:800 1 07, 2021 12:05:24 午後 Chap14.Example6 main 重大: null Chap14.OriginalException: 残高不足です at Chap14.Player.withdraw(Example6.java:24) at Chap14.Example6.main(Example6.java:11) |
void withdraw(int amount) throws OriginalException
とあるのは、OriginalExceptionという例外を投げる可能性のあるメソッドだということを示しています。
もしも、これを書かないと例外の発生源であるこのメソッド内でtry-catchしなけばなりません。
しかし、例外処理はトラブルが起こったことの報告でした。
会社のトラブルの報告でも、直接対応した一般社員から上司に報告を上げることがありますね。
同じように、Javaでは例外の報告を使用者に報告するのが基本原則です。
例外を発生源で握りつぶすのではなく。
IDEを使っていると6,7行目のwithdrawメソッドのところで警告が出ると思います。
つまり、チェック例外なので、try-catchで囲みなさいということですね。
IDEを使っていれば簡単にtry-catchで囲めますので講師に聞いてやってみてください。
私の場合はこんな風になりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package Chap14; public class Example6 { public static void main(String[] args) { Player p1 = new Player(); p1.withdraw(200); p1.withdraw(1000); } } class Player { private int balance = 1000; void withdraw(int amount) throws OriginalException { if ((balance - amount) < 0) { OriginalException e = new OriginalException("残高不足です"); throw e; } else { balance -= amount; System.out.println("現在の残高です:" + balance); } } } class OriginalException extends Exception { public OriginalException(String message) { super(message); } } |
このように、呼び出し元に例外の発生を知らせるのがよいJavaプログラムです。
つまり、ここまでをまとめると、
自分で作成した例外オブジェクトをthrowした後の処理は2通りあります。
①try~catch文で囲んで処理する
②メソッドの呼び出し側で処理する
そして、②の場合はメソッドの記述が以下のように変化しました。
戻り値 メソッド名(引数) throws 例外の型 { 例外を投げる可能性のあるメソッドの内容 } |
throwとthrowsで“s”が付くか付かないかが分かりにくかったかもしれません。
実際に投げる際には動詞のthrow
このメソッドは例外を投げるメソッドであるというときは名詞の複数形でthrows
と覚えていただくと良いと思います。
今回は例外処理について見てきました。
例外処理を使うことでプログラムはトラブルの発生をユーザーに伝えることができ、途中終了を避けることができるのでした。
また、継承の仕組みを上手く応用しているのも興味深いところでしたね。
次回のテーマはコレクションフレームワークです。
コレクションフレームワークを一言で言うと便利なメソッドを持った配列です。
配列では出来なかった要素の追加変更、削除も可能です。
ということで、次回を楽しみに待っていてください。
JavaSE8の解説に戻る