プログラムを開発するにあたって、実行時に想定外の事態が発生する可能性があることを考慮しておく必要がある。
そして、そのような場合にも異常終了や誤作動しないように備えておかなければならない。
Javaには想定外の事態に対処する機能が備わっている。
例外処理を行うことで、実行時エラーに対処できる。
エラーの種類と対応策
●不具合のないプログラムを目指す。
動くコードは書けて当たり前。不具合対策が出来てこそエンジニアである。
(不具合:プログラムが想定通りに動かないこと)
エラーには、「文法エラー」、「実行時エラー」、「論理エラー」の3種類ある。
●3種類の不具合と対処法
①文法エラー(syntax error)
文法の誤りによりコンパイルに失敗する。
代表例はセミコロン忘れ、変数名の間違い、privateメソッドを外部から呼び出すなど。
②実行時エラー(runtime error)
実行している最中に何らかの異常事態が発生し、動作が継続出来なくなるエラー。
Javaの文法としては問題がないためコンパイルは成功し、実行も出来るが、実行中にエラーメッセージが表示されて強制終了する。
代表例は、配列の範囲外要素へのアクセス、0での割り算、存在しないファイルのオープンなど。
③論理エラー(logic error)
Javaの文法に問題はなく、強制終了もしない。しかし、プログラムの実行結果が想定していた内容と違っているケース。代表例は、電卓ソフトを作ったものの計算結果がおかしいなど。
★下記は、不具合の検出と解決方法↓
※文法エラーと論理エラーは対策が似ている。
●例外的状況
文法エラーと論理エラーは「開発者の損失」であって、「開発者が開発時にテストをしっかり行い、コードを修正することで、本番での発生が予防できるもの」である。しかし、実行時エラーはそうはいかない。
そもそも実行時エラーは「プログラム実行中に想定外の事態が発生したこと」によって起こる。
この想定外の事態を、「例外的状況/例外」と言う。
例外的状況には、次のようなものがある。
・パソコンのメモリが足りなくなった
→開発用のコンピュータには十分な容量のメモリがあったが、本番用コンピュータのメモリが少なく、
動作中にメモリが足りなくなってしまった。
・存在すべきファイルが見つからない
→動作中にdate.txtというファイルを読み込んで動くプログラムを開発したが、利用者が誤ってファイルを削除してしまった。
・nullが入っている変数を利用しようとした
→ユーザーが想定外の操作を行ったことが原因で、本来は変数に入るはずのない値(nullなど)が入り、その変数を利用するメソッドを呼び出してしまった。
事前にif文でチェックすれば予防できると思うかもしれないが、プログラムに含まれる全てのメソッドを呼び出してnullチェックを行うのは現実的ではない。
これらの共通点は「プログラマがソースコードを作成する時点では例外的状況の発生を予防できない」ということ。プログラマが「例外的状況の発生を防ぐこと」は困難。しかし、「もし例外が発生したときに、どのように対処するか」という対策を用意することは可能。
例えば、「もし目的のファイルが見つからなければ、ユーザーに代わりのファイル名を入力してもらう」など、異常事態に備えた代替策を準備しておけば良い!
このように例外的状況に備えて対策を準備し、その状況に陥った際に対策を実施することを
「例外処理」と呼ぶ。
「例外処理を行うことで実行時エラーに対処できる」
例外処理の流れ
従来の例外的状況を1つ1つチェックする命令文を書くとなると、以下の問題点がある。
・「本来の処理」が、どの行なのか分かり辛い。
・命令を呼び出す度に1つひとつ例外的状況をチェックしなければならない。
・面倒で例外処理をサボって書かないおそれがある。
これを解決する例外処理専用の文法と仕組みが備わっている…↓
Javaでは例外処理にtryとcatchという2つのブロックを使用する。合わせて「try-catch文」という。
「通常、実行されるのはtryブロック」だけでcatchブロックの処理は動かない。
ただし、「tryブロック内を実行中に例外的状況が発生したことをJVMが検知すると、処理は直ちにcatchブロックに移行」する。catchブロック内には「例外的な状況が発生した時に実行される処理」を記述しておく。
例外処理の基本パターン
try {
通常実行される文
} catch(…) {
例外発生時に実行される文
}
例外クラスとその種類
●例外を表すクラス
一口に例外的状況と言っても、「ファイルがない」、「メモリが足りない」、「変数がnull」など様々な状況がある。これらと同じものとして扱うと、「発生した例外的状況に応じた処理を行う」ことができない!!そこでjavaでは、発生した例外を区別できるように、「それぞれの例外的状況を表すクラス」が複数準備されている。
現実世界の例外的状況(想定外の事態)をクラスにしたものが「例外クラス」である。
例)「java.io.IOException」は「ファイルの読み書きなどの入出力ができなくて困っている状況」のためのクラスであり、他にも多くの例外クラスがAPIとして定義されている。
●例外の種類
APIで提供されている例外クラスは、次の図のような継承階層を構成している。
例外クラスは、「Error系」「Exception系」「RuntimeException系」に大別できる。
①Error系例外
java.lang.Errorの子孫で「回復の見込みが無い致命的な状況」を表すクラス。
代表的なものには、「OutOfMemoryError(メモリ不足)」や「ClassFormatEroor(クラスファイルが壊れている)」。通常このような状況をキャッチしても打つ手はないため、キャッチする必要はない。
②Exception系例外
java.lang.Exceptionの子孫(RuntimeExceptionの子孫を除く)で、「その発生を十分に想定して対処を考える必要がある例外的状況」を表すクラス。
例えば、「IOException(ファイルなどが読み書きできない)」、「ConnectException(ネットワークに接続できない)」といった状況は、ファイルやネットワークを利用する際に当然想定しておくべき事態。
③RuntimeException系例外
java.lang.RuntimeExceptionクラスの子孫で、「必ずしも常に発生を想定すべきとまでは言えない例外的状況」を表すクラス。
例えば、「NullPointerException(変数がnullである)」、や「ArrayIndexOutOfBoundsException(配列の添え字が不正)」のように、いちいち想定していると「キリがない」ものが多く含まれる。
●チェック例外
「Exception系例外」は、その発生を十分に想定して対処を考えておく必要がある状況を表しているのだから、「いざ、例外が発生したときに何も対処されないということはあってはならない」のである。
そのため、「Exception系の例外が発生しそうな命令を呼び出す場合、try-catch文を用いて『例外が発生した時の代替処理』を用意しておかないとコンパイルエラー」になる。
//例外処理を用意せずにエラーになる
import java.io.*;
public class Main {
public static void main(String[] args) {
FileWriter fw = new FileWriter("data.txt");
/* : */
}
}
/*コンパイル結果
Main.java:5:
例外 IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります。
*/
上記は
FileWriterのコンストラクタはIOExceptionを発生させる可能性があるが、try-catch文を記述していない。(失敗時にどうするか考えていない)
そこで次のようにtry-catch文を用いてIOExceptionの発生に備えれば、コンパイルエラーはなくなる。
//try-catch文でException系例外の発生に備える
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("data.txt");
/* : */
} catch(IOException e) {
//例外的状況になったときに備えて記述された代替処理
System.out.println("エラーが発生しました。");
}
}
}
このように、Exception系例外は、例外発生時の対策が用意されているかをコンパイルの時点でチェックされるため、「チェック例外」とも呼ばれる。
3つの例外クラスのグループとキャッチの強制
・Error系例外… try-catch文でキャッチする必要はない。
・Exception系例外… try-catch文でキャッチしないとコンパイルエラー。
・RuntimeException系例外… try-catch文でキャッチするかは任意。
●発生する例外の調べ方
「どのクラスの、どのメソッドが、どのような例外を発生させる可能性があるか」という情報はAPIリファレンスに掲載されている。
リファレンスには、
メソッドやコンストラクタを呼び出す際にException系の例外が発生する可能性がある場合、
「引数リストの後に『throws 例外クラス名』が表記される。」
例)APIリファレンスに記載されている、FileWriterクラスのコンストラクタの解説(抜粋)
つまり→「FileWriterのコンストラクタを呼び出す(インスタンスを生成する)ときには、IOExceptionをキャッチするtry-catch文が必要になる」と理解すればよい!!!
例外の発生と例外インスタンス
●例外インスタンスの受け渡し
「catch(IOException e)」の「e」とは??
tryブロック実行中はJVMが例外的状況の発生を監視しながらプログラムを実行する。
そしていざ例外が発生すると、JVMは処理をcatchブロックに移行する。
このときJVMは、「プログラムの中のどこで、どのような例外が起きたのか」という「例外的状況の詳細情報が詰め込まれたIOExceptionインスタンスをcatch文で指定された変数eに代入する」。
catchブロックの中では、この変数eに格納された詳細情報を取り出して、適切なエラー処理(画面にエラーメッセージとして表示するなど)を行うことができる。
●例外インスタンスの利用
例外インスタンスに格納されている詳細情報は、その例外の種類によって異なる。
しかし、全ての例外は「例外的情報の解説文」と「スタックトレース」の情報を必ず持っており、それぞれ以下のメソッドで取得と表示ができる。
※例外インスタンスが必ず備えているメソッド
戻り値 | メソッド | 機能 |
---|---|---|
String | getMessage() | 例外的状況の解説文 (いわゆるエラーメッセージ)を取得する |
void | printStackTrace() | スタックトレースの内容を画面に出力する |
「スタックトレース」とはJVMがプログラムのメソッドをどのような順序で呼び出し、どこで例外が発生したかという経緯が記録された情報。「e.printStackTrace();」という文をcatchブロック内に記述すれば、その内容を画面に表示できる。
よく見てきている実行時エラーが発生したときのエラー画面がスタックトレースの内容である。
例外が発生してもtry-catch文でキャッチされなかった場合、JVMがプログラムを強制停止してスタックトレースの内容を画面に表示しているのである。
様々なcatch構文
●try-catch構文の基本形
try-catchの基本構文
try {
本来の処理
} catch(例外クラス 変数名) {
例外が発生したときの処理
}
なお、例外インスタンスを受け取るための変数名は自由であるが、慣習として「e」「ex」が使用されている。
●2種類以上の例外をキャッチする
try-catchの基本構文でキャッチできるのは1種類の例外だけ。しかし、catchブロックを複数記述することが出来る。
JVMが発生した例外の型に対応するcatchブロックを上から順に検索し、最初にキャッチできたcatchブロックに処理を移す。
//
try {
//本来の処理
FileWriter fw = new FileWriter("data.txt");
fw.write("hello");
fw.close();
} catch(IOException e) {
//ファイルへの書き込みが失敗したときの例外処理
System.out.println("書き込みが失敗したよ");
} catch(NullPointerException e) {
//nullだったときの例外処理
System.out.println("nullだよ");
}
上記のようにcatchブロックを2つ記述しているが、
IOException、NullPointerExceptionのどちらを捕まえても同じ処理をする場合、下記のようなcatchブロックを1つにまとめることも可能。
「catch(IOException | NullPointerException e){…}」
●ザックリと例外をキャッチする方法
ザックリ捉えてしまってもよい!!
例外クラスの継承階層図によれば、「IOException」も「NullPointerException」もザックリ捉えればどちらも「Exception」である。よって次のように記述することで、どちらの例外が発生しても1つのcatchブロックでキャッチできる。
//大雑把なcatch
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("data.txt");
fw.write("hello!");
fw.close();
//Exceptionの子孫をどれでもキャッチ
} catch(Exception e) {
System.out.println("何らかの例外が発生しました");
}
}
}
●~後片付け処理への対応~ try-catch-finally構文
実は前述のコードには致命的なバグがある。それは、9行目あたりの「ファイルを閉じる処理が実行されない(ファイルが開いたままになっている)」こと。
例えば、8行目のfw.write("hello!");を実行したとき、偶然ディスク容量が不足してIOExceptionが発生した場合、処理はcatchブロックに移動してしまうため、9行目のfw.close();は実行されないで、開いたままになってしまう。
ファイルは開いたら閉じるが決まりである。そのため、例外が起きても起きなくても必ず実行しなければならない処理。
注意!!以下のプログラムもエラーになってしまう。
//try-catchの後でcloseするとエラーになる
/*省略*/
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("data.txt");
fw.write("hello!");
} catch (IOException e) {
System.out.println("エラーです");
}
//ここ!!!
fw.close();
}
/*省略*/
上記エラーになる理由:もしtryブロックの中でNullPointerExceptionなどが発生した場合、例外はキャッチされないので、ファイルを閉じないままプログラムを強制終了してしまう。
なお、fw.close();の行ではコンパイルエラーが発生する。
fw.close();は、IOException送出する可能性があるため、「fw.close();」も別途try-catchで囲まなければならない。
後片付け処理は、『例外が発生しても、またはしなくても、たとえ強制終了になるときでも、必ず実行しなければならない処理』
そして、そのような処理をJVMに確実に実行させるために、「finallyブロック」を用いる。
例外発生の如何を問わず必ず処理する [try-catch-finally構文]
try {
本来の処理
} catch(例外クラス 変数名) {
例外が発生したときの処理
} finally {
例外があってもなくても必ず実行する処理
}
一度JVMが「tryブロックの実行を開始したら、必ず最後にfinallyブロックの内容も実行されることが保証」される。
「後片付け処理には、必ずfinallyを使う」。
finallyを使うべき状況
後片付け処理は、必ずfinallyブロックに記述する。
(ファイル・データベース接続・ネットワーク接続など)
●close()にまつわる複雑なエラーと解決方法
//finallyブロックでcloseすると…エラーになる。
/*省略*/
try {
FileWriter fw = new FileWriter("data.txt");
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
} finally {
//この行でエラーが出る
fw.close();
}
/*省略*/
//実行結果
Main.java11: エラー:シンボルを見つけられません
fw.close();
上記を解決するには…
①「変数fwが見つからない」エラーを解決する
変数のスコープが原因。
改善↓
import java.io.*;
public class Main {
public static void main(String[] args) {
//変数fwのスコープを広げる
FileWriter fw = new FileWriter("data.txt");
try {
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
} finally {
fw.close();
}
}
}
しかし!上記のままでは、
FileWriter fw = new FileWriter("data.txt");でエラーが起きる。
②「new FileWriter()をtry-catchしていない」エラーを解決する
上記のプログラムをコンパイルすると「例外 IOExceptionをキャッチしていない」という内容のエラーが出る。
改善↓
import java.io.*;
public class Main {
public static void main(String[] args) {
//mainメソッドブロックはfwのスコープのまま
FileWriter fw;
try {
//tryブロック内でコンストラクタが動作する
fw = new FileWriter("data.txt");
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
} finally {
fw.close();
}
}
}
上記のままではまだエラーに…。
③「初期化されていない変数fwを利用する可能性がある」エラーを解決する
上記コードをコンパイルすると、「fw.close();」の行で「初期化されていない変数fwを利用してしまう可能性がある」という内容のエラーが発生する。これは、次のようなケースでは「中身が不明な変数を利用しようとしてしまう」ことを理由にコンパイラがエラーを出している。
(1)「FileWriter fw;」で変数fwが宣言される。
(2)「try {…」でtryブロックに入る。
(3)「fw = new FileWriter("data.txt");」の右辺を実行しようとした瞬間に何らかの例外が発生し、処理がcatchブロックに移行する。
(4)「System.out.println("何らかの例外が発生しました");」が実行されたあと、finallyブロックに処理が移行する。
(5)「fw.close();」で変数fwを利用しようとするが、fwは初期化されてい無い状態(nullさえ代入されていないので、内容が「不明」な状態)である。
そのため、「FileWriter fw;」の行は、以下のようにして仮にnullを代入しておくことでエラーを消すことができる。
FileWriter fw = null;
しかし、しかしまだエラーは続く…
④「fw.close()をtry-catchしていない」エラーを解決する。
上記のように修正しても、コンパイルするとfw.close();の行でエラーが発生する。
今回のエラーは、「例外をtry-catchしていない」という本質的なもの。
APIリファレンスを確認すると記載があるが、fw.close();の行で呼び出している「close()」もIOExceptionを発生させる可能性があるメソッド。よって、次のようにtry-catchブロックで囲まなければならない。
//後片付け処理もtry-catchする
/*省略*/
FileWriter fw = null;
try {
fw = new FileWriter("data.txt");
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
} finally {
try {
fw.close();
} catch (IOException e) {
;
}
}
/*省略*/
上記の最後の方にある「;」とは??
これは「空文」という構文である。JVMは空文にあっても何も処理はされない。
これでコンパイルは通るが、最後の詰めをしっかり行おう!
⑤「NullPointerExceptionの発生」に対応する。
コンパイルエラーは出なくなるが、もし何らかの理由で、fw = new FileWriter("data.txt");の行で右辺で例外が発生すると、その後動作するfw.close();では、fw内にnullが入っているので「NullPointerException」が発生してしまう。
fwがnullだということは、ファイルのオープンに失敗しているので、そもそもcloseする必要はないため、次のように修正する必要がある。
//ファイルを開いたときだけ後片付けする
import java.io.*;
public class Main {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("data.txt");
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
} finally {
//fwがnullではないときだけclose()を試みる。
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
;
}
}
}
}
}
●~自動的にclose()が呼ばれるtry-catch文~ 「try-with-resources文」
例外のせいで複雑な処理を書かなければいけない。そんな悩みを解決する新しい構文が「Java7」から追加された。
tryの直後に丸括弧で囲まれた複数の文を記述することが可能になった。「try-with-resources文」。
ここで開かれたファイルやデータベース接続などは、finallyブロックを記述しなくてもJVMによって自動的にcloseメソッドが呼び出される。
前述のプログラムを次のようにスッキリと書くことでできる。
//try-with-resources文の利用
import java.io.*;
public class Main {
public static void main(String[] args) {
//try-catch文を抜ける際に、自動的にclose()が呼び出されるので、finallyブロックの記述不要
try (FileWriter fw = new FileWriter("data.txt");) {
fw.write("hello!");
} catch (Exception e) {
System.out.println("何らかの例外が発生しました");
}
}
}
なお、JVMによって自動的にクローズされるのは、java.lang.AutoCloseableインタフェースを実装しているクラスに限られる。ファイル操作やデータベース接続、ネットワーク接続に用いるAPIクラスの多くは、
AutoCloseableを実装しているため、try-with-resources文での簡潔な記述が可能。
try-with-resources文
try (closeによる後片付けが必要な変数の宣言) {
本来の処理
} catch(例外クラス 変数名) {
例外が発生したときの処理
}
※finallyを記述せずとも自動的にcloseされる。
※宣言する変数はAutoCloseableを実装している必要がある。
例外の伝播(デンパ)
●mainメソッドで例外をキャッチしないと…
例外が発生したにも関わらずキャッチしないと実行時エラーとなり、プログラムは強制終了してしまう。
「例外が起きても何もできない=お手上げ」となり、JVMはスタックトレースを画面に表示して、仕方なく強制終了するのである。それでは、mainメソッドではなく、呼び出した先のメソッドで例外が発生したときはどのような動作になるのか。
次のプログラムを例に考えてみよう!
・mainメソッドの中ではsubメソッドを呼んでいる。
・subメソッドの中ではsubsubメソッドを呼んでいる。
・subsubメソッドでは処理中に何らかの例外が発生することがある。
subsubメソッド実行時に例外が発生すると、JVMの内部では次のようなことが発生する。
↓↓
①まずsubsubメソッドで例外をキャッチしていなければ(try-catch文がなければ)、「subsubメソッドとしては、この例外的状況に対してお手上げ」となり、呼び出し元のsubメソッドに対応が委ねられる。
②subメソッドでもキャッチしないと、その対応はmainメソッドに委ねられる。
③mainメソッドで例外をキャッチしなければ強制終了する。
このように例外はキャッチされない限り、メソッドの呼び出し元まで処理を「たらい回し」にされてしまう。
この現象を「例外の伝播」という。
もちろん呼び出し元のmainメソッドやsubメソッドにcatchブロックが準備されていれば例外の伝播はそこで止まる。
●チェック例外の伝播とスロー宣言
例外の伝播は、発生した例外が各メソッドでキャッチされず「お手上げ」になるために起こる。Exception系例外(チェック例外)の場合は、try~catch文によるキャッチが必須であるため、基本的に例外の伝播は発生しない。
しかし、メソッドを宣言する際に「スロー宣言」を行うことで、「発生するチェック例外をキャッチせずに呼び出し元へ伝播させることが許可される」。
「スロー宣言」による例外伝播の許可
アクセス修飾 戻り値 メソッド名(引数リスト) throws例外クラス1, 例外クラス2, ・・・ {
メソッドの処理内容
}
//例外
public static void subsub() throws IOException {
FileWriter fw = new FileWriter("data.txt");
}
上記ではtry-catchがない為、エラーになるかと思うかもしれないが、
「スロー宣言」があるときに限ってtry-catch文がなくてもコンパイルエラーにならない。
スロー宣言すると、コンパイルエラーにならないのは何故か…
スロー宣言とは、そのメソッドが「私はメソッド内でチェック例外が発生しても処理しませんが、『私の呼び出し元が処理します』」と表明する宣言だからである。
その一方、スロー宣言が含まれるメソッドを呼び出す側は「このメソッドを呼び出すと、呼び出し先で発生した例外が処理されずに『自分に伝播してくる可能性がある』」ことを覚悟しなければならない。
スロー宣言が及ぼす影響
影響①:呼び出される側のメソッドは、メソッド内部での例外キャッチが「義務ではなくなる」。
影響②:呼び出す側のメソッドは、例外を伝播してくる可能性があるメソッド呼び出しをtry-catch文で囲む「義務が生まれる」。
チェック例外に対する処理方法(まとめ)
全てのメソッドは「チェック例外をどう処理するか」について次の2つの方針のどちらかを採用し、その方針ごとに課される義務を果たさなければならない。
例外処理方針①:チェック例外を自分で処理
【方針の意味】
「私は自分で例外的状況を解決します。例外が発生してもお手上げはせず、呼び出し元に迷惑はかけません。」
【この方針を採用することで課せられる義務】
発生する可能性がある、全てのチェック例外をtry-catch文で処理すること。
例外処理方針②:チェック例外を処理せず、呼び出し元に委ねる
【方針の意味】
「私は自分で例外的状況を解決できません。例外が発生したら、呼び出し元に処理を任せます」
【この方針を採用することで課せられる義務】
メソッド定義にスロー宣言を加え、委ねる例外の種類を表明すること。
例外を発生させる
●例外的状況をJVMに報告する
「例外的状況が発生したかどうか」はJVMが監視している。そしてJVMは例外的状況を検知すると処理をcatchブロックに移す。実は、この監視をしているJVMに対して、私たち自身が「例外的状況になりました」と知らせることが出来るのである。
例外的状況が発生したことを報告するには次の構文を使う。
例外的状況の報告(例外を投げる)
throw 例外インスタンス;
※一般的には「throw new 例外クラス名("エラーメッセージ");」となる。
監視中のJVMに例外的状況を報告することを「例外を投げる/例外を送出する」ともいう。
例外が投げられるとJVMはそれを検知し、即座にcatchブロックの実行や例外の伝播に処理を移す。
例外インスタンスを自分で投げる例(2つのプログラムセットで考える)
public class Person {
int age;
public void setAge(int age) {
if (age < 0) { // ここで引数をチェック
throw new IllegalArgumentException("年齢は正の数を指定すべきです。指定値=" + age);
}
this.age = age; // 問題ないなら、フィールドに値をセット
}
}
public class Main {
public static void main(String[] args){
Person p = new Person();
//誤った値のセットを試みる→例外発生
p.setAge(-128);
}
}
実行結果
Exception in thread "main" java.lang.IllegalArgumentException: 年齢は正の数を指定すべきです。指定値=-128
at Person.setAge(Person.java:5)
at Main.main(Main.java:4)
PersonクラスのsetAgeメソッドでは、引数をフィールドage(年齢)に代入する。
しかし、ageが負の値になることを防ぐため、代入の前に引数を検査している。
もし引数に問題がある場合は、IllegalArgumentExceptionインスタンスを投げることで「引数が異常で処理を継続できない」という例外的状況に陥ったことをJVMに報告する。
setAgeメソッドで発生した例外は、呼び出し元のmainメソッドに伝播する。
しかし、ここでは例外をキャッチしていないので、最終的にJVMがプログラムを強制停止する。
※スロー宣言で使うthrowsと、例外的状況の報告に使うthrowは全くの別物!!!
●オリジナル例外クラスの定義
独自の例外的状況を表す「オリジナルの例外クラス」を使いたくなるケールもあるかもしれない。
例)音楽プレイヤーソフトを開発しているのであれば、「対応していない形式のファイルを再生しようとした」などの例外的状況を表すクラス(例えば、UnsupportedMusicFileException)が必要になるかもしれない。
そのような場合、「既存の例外クラスを継承してオリジナルの例外クラスを作る」ことができる。
継承元になる例外クラスは、チェック例外を表すExceptionや、非チェック例外を表すRuntimeExceptionの他、IOExceptionなど実際に何かの状況を表している例外クラスでも構いません。
しかし、ThrowwableやErrorの子クラスとしてオリジナル例外を定義することはほとんどないでしょう。
//オリジナル例外を定義する
public class UnsupportedMusicFileException extends Exception {
// エラーメッセージを受け取るコンストラクタ
public UnsupportedMusicFileException(String msg) {
super(msg);
}
}
//オリジナル例外を利用する
public class Main {
public static void main(String[] args) {
try {
// 試験的に例外を発生させる
throw new UnsupportedMusicFileException("未対応のファイルです");
} catch (Exception e) {
e.printStackTrace();
}
}
}
本格的で大規模なプログラムを開発するときは、想定されるさまざまな例外的状況を思い浮かべ、
オリジナルの例外クラスとして作成することで、きめ細かい実行時エラーへの対処が可能になる。