見出し画像

【Javaお勉強日記】Javaの例外処理についてまとめる

例外とは

メソッドが「自分じゃ処理できません!」って上げる悲鳴のこと。

例えば「割り算をするけど、割る数に0が入ってきてたらエラーを返すよ!」っていう仕様のメソッドを作りたいとする。

これがpythonみたいな型ゆるゆる言語なら、(お行儀は悪いけど)「nullが返ってきたらエラー。上手く行ったらdouble値が返ります」とか、全部{success:true,result:5}みたいなオブジェクトで返す、とかでも対応できなくはない(お行儀は悪い)。

でも、Javaは型キッチリ言語なので、doubleが返ると決めたメソッドからはdoubleしか返せない。「0が返ってきたらエラー」とかしようにも、本当に計算結果が0だった場合までエラー扱いになっちゃう。

なので、「こいつはこのメソッドじゃ処理できません!」って悲鳴を上げて、悲鳴を聞きつけた呼び出し元のメソッドに処理を丸投げする。それが例外。

というわけで、Javaの場合エラーへの対処は基本的に例外処理の仕組みを使わないといけない。

例外の投げ方

メソッドが悲鳴を上げることを「例外を投げる」という。投げる時はthrow文を使って、

throw new NanikanoException("任意のエラーメッセージ")

という形で書く。

NanikanoException と書いた所は、用意されている既存の例外クラスの中から状況に応じて適切なものを選んで入れる。(例外クラスのインスタンスを作って投げている)

例外は沢山あるので、どんな例外があるかは、必要になったとき調べれば十分じゃないかな……

例外の受け取りかた

悲鳴を上げるだけ上げても聞きつけて貰えないと意味がないので、例外を投げる可能性のあるメソッドを呼び出すときには、呼び出し側で「こいつ悲鳴上げるかもしれないからよく聞いとこ」「悲鳴聞こえたらこうしよ」と予め準備しておく。

try{
    // 例外を投げるかも知れないメソッドと、そのメソッドの結果がないと動かない処理を書く
}
catch(例外の型 例外を受け取る為の変数){
    // 例外が投げられた時にどう対応するかを書く
}

それが、tryブロックとcatchブロック。IF文みたいに書けばいい。if ~ else の代わりに try ~ catch。

try節の中で例外が発生したら、その時点でtry節内の全ての処理はすっ飛ばされて、catch節へ処理が移動する。

catch節は、例外を受け取る為の引数を取る、ので、その引数の型も決めておかないといけない。つまり、どんな例外を受け取るかを決めておく必要がある。決めておいた例外以外の例外クラスが飛んできてもキャッチできないので注意する。

複数の例外が発生する可能性がある場合、catch節は必要に応じて増やせる。(if ~ else文みたいにどんどん繋げていけばいい)

但し、サブクラス例外から順番に受け取っていかないといけない。(先にスーパークラス例外catchしちゃったら、サブクラス例外も全部そこに捕まっちゃうから)

なので、使いたい例外がどの例外のサブクラスなのか、スーパークラスなのか、ちゃんと確認しておかないとコンパイルエラーになるから注意だぞ。

逆に、まとめてキャッチしたい場合はスーパークラスでcatchすればまとめて捕まる。ただし例外内容による細かい処理分岐は出来なくなるので注意。

例外の伝播

例外の伝播

try~catchのあるメソッドが、別のメソッドを呼び、そのメソッドがまた別のメソッドを呼んで処理していて、中継点のメソッド内にtry~catchがない場合、送出された例外はどんどん呼び出し元メソッドに送られるので、最終的にcatchできれば、場合によってはtry~catchを書かなくても良いケースもある。

※ただし、チェック例外(後述)は、自動では呼び出し元に送ることができないので注意。(宣言しておけば大丈夫)

(……けど、基本的にはちゃんと一個ずつ捕まえないと訳分からなくなると思う)

チェック例外と非チェック例外(実行時例外)

発生箇所が限定されているため、必ず自分でtry~catchをしないと使えないのがチェック例外。

いつでもどこでも起きちゃう可能性があるため、try~catchを書かなくても動くのが非チェック例外(実行時例外)。

チェック例外は例えばファイル入出力に関わる例外(ファイルがみつかりません、とか)データベース処理に関わる例外とか。

非チェック例外は、0で割ろうとしたエラーとか、配列に存在しないインデックス番号を参照しようとしたエラーとか。

で、自分が書く場合は忘れずに例外処理を書くだけで良いけど、既存のパッケージとかがチェック例外を投げる事がある場合は要注意。

チェック例外の投げ方

チェック例外を投げるメソッドは、「僕は!この例外を!投げることがあります!」と宣言しておかないといけない。しないとコンパイルエラーになる。

宣言は、メソッドを宣言した後に throws 例外クラス名 の形式で書く。

public static void hoge() throws NanikanoException{ ... }

複数種類投げる可能性がある場合、

public static void hoge() throws Exception1, Exception2, ...{ ... }

と列挙する。

宣言のときはsが付く事に注意。(三単現のsなんかな……)

チェック例外のスルーのしかた

先ほど「ただし、チェック例外(後述)は、自動では呼び出し元に送ることができないので注意。(宣言しておけば大丈夫)」と書いたところ。

スルーするメソッドは、「僕はチェック例外が来てもスルーしまーす!」という宣言が必要。

public static void ExceptionWoMushi() throws Exception{ ... }

と、チェック例外のスーパークラスであるExceptionを投げる宣言しておくと、スルーできる。

例外クラスが持ってるメソッド

例外もクラスなのでメソッドを持っている。似てるけど微妙に違うので、使いどころに応じて使い分ける。

toString()→エラー名とエラーメッセージ文字列を返す

printStackTrace()→エラー内容とスタックトレースを表示する

getMessage()→エラーメッセージ文字列を返す

例外クラスのスーパークラスであるThrowableクラスが持ってるので、どの例外クラスにも必ずある。

自作例外クラスの作り方

既存の例外で表現しきれない例外については、Throwableクラスを継承して、自作の例外クラスを作ることが出来る。

正確には、チェック例外を作る時はExceptionクラス、非チェック例外を作るならRuntimeExceptionクラスを継承して作る。(それぞれThrowableのサブクラス)

public class SampleException extends Exception{
    public SampleException(String message){
        private static final long serialVersionUID = 1L;
        super(message);
       }
    public SampleException(){
        super(); // 引数のないsuper();は省略可能
    }
}

継承したいスーパークラスをextendsして、メッセージ文字列を引数に取る場合と取らない場合、二種類のコンストラクタを作れば完成。

……なんだけど、なんかエディタに怒られるので、エディタに任せて自動修復を掛けてみると

private static final long serialVersionUID = 1L;

というのが追加される。

これは、ExceptionのスーパークラスであるThrowableクラスが、Serializableインターフェイスを実装している為。らしい。詳細はよくわからないけど、Serializableを実装したクラスにはserialVersionUIDが必要なんだそうです。

(詳細は分からないけど「serialVersionUID」があれば動くんだね!っていうのはまさしく「インターフェイス」のお仕事なのではないだろうか)

でもこれがちゃんと書いてあると「ちゃんと管理してるよ!」っていう風に見えちゃうので、ちゃんと管理出来てないなら書かない方が良いとも聞く。そこんところ、結局どうしたら良いのかは……チームの判断によるのかな……


と、言うわけで例外なんて他の言語でも散々使ってるんだから解ってるさ大丈夫さ!と思っていたら、意外と記法の点で抑えることが多かった例外関係のお話でした。

この記事が気に入ったらサポートをしてみませんか?