Java / 基礎 / 例外

Java / 基礎 / 例外

Java プログラムでも設計が特に難しいのがこの例外だと思う。

基本

Java の例外処理は例外クラスのインスタンスを throw する それを catch するという動きで実現されている。

メソッド中でこのように使う

try{
    throw new HogeException("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(HogeException e){
    // インスタンスが throw されるとここに来る
}
// catch で何もしなければ普通の制御通り下に流れてここに来る。

例外が throw される可能性があり、それを捕捉したい場合その範囲を try のブロックで囲む。 この囲んでいるブロックも Java の文法通り、スコープが分離されるので注意が必要である。

try で囲んでいる範囲で例外が throw されると(例では無条件で発生させている)、全部の流れを無視して後続の catch へ処理がジャンプする。 なので、try ブロック中の例外発生地点以降は処理が実行されない。

発生した例外、つまり例ならば new されている HogeException クラスのインスタンスが catch に書かれているメソッドの引数のような変数(例ならば e)に格納されて処理が渡される。例外の処理は普通そんなに長くなく、対象は自明なので、慣例として「e」のような短い変数が使われる。

複数 catch

try 1つに対して catch ブロックは複数書くことができて例外の型によって自動分岐される

メソッド中でこのように使う

try{
    throw new HogeException("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(HogeException e){
    // HogeException インスタンスが throw されるとここに来る
}catch(PiyoException e){
    // PiyoException インスタンスが throw されるとここに来る。
}

分岐したあとは switch 文のように後続の catch に処理が流れていくことは無い。

ではこの複数の例外クラスで catch した後の処理がまった同じだった場合、これを共通で書く書き方もある

try{
    throw new HogeException("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(HogeException|PiyoException e){
    // 例外処理
}

このように書ける

スーパークラス catch

型が完全一致しなくても、その親クラスで catch することもできる。

try{
    throw new HogeException("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(Exception e){
    // インスタンスが throw されるとここに来る
}
// catch で何もしなければ普通の制御通り下に流れてここに来る。

Exception クラスを継承した HogeException クラスを使って親子多段にした場合。 型が詳細なほうに合致すればそっちにジャンプする。

try{
    throw new HogeException("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(HogeException e){
    // インスタンスが throw されるとここに来る
}catch(Exception e){
    // インスタンスが throw されるとここに来る
}
// catch で何もしなければ普通の制御通り下に流れてここに来る。

これも複数 catch のルール同様、スーパークラスだったとしても、処理が次のブロックに流れていったりしない。

catch 中の throw

catch したそのブロックの中で throw することもできる。 これはよくあるパターンで、catch した詳細で汎用的な例外を仕様に合わせたより専門的な例外に作り変えてしまうというパターン

try{
    throw new Exception("れいがいだよ");
    // ここ以降は実行されない
    // ここ以降は実行されない
}catch(Exception e){
    throw new HogeException(e);
}

これも複数 catch でも同様で、サブクラス側の catch ブロックで親クラスのインスタンスを throw したとしても、後続の親クラスの catch ブロックには処理は進まない。 catch はあくまで先行して書いてある try ブロックの中の例外発生時のハンドリングを書く場所になる。

入れ子例外処理

このように catch の中でさらに例外発生をハンドリングすることができる。

try{
    throw new HogeException("れいがいだよ");
}catch(HogeException e){
    try{
        throw new Exception();
    }catch(Exception e){
        //例外への対処
    }
}catch(Exception e){
 
}

逆に言うと例外機構での分岐はこのような入れ子構造に必ずなってしまうので、 こいつを用いて何か複雑な処理分岐を考えないほうがよい、見通しが非常に悪いし、そもそもそういう機能ではない。

catch 無し finally

finally を書くのに catch 節は必須ではない。なのでこのように書ける。

try{
    hoge();
}finally{
    piyo();
}

この場合、 hoge メソッドが例外を発生した場合、catch してないのでそのまま try ブロックの外に例外が抜けていってしまうのだが、その前にしっかり finally が実行される。

なのでこのようなコードは書かなくてヨイ

try{
    hoge();
}catch(Exception e){
    throw e;
}finally{
    piyo();
}

catch ではない例外処理

catch ではないというか、メソッドに throws という項目を書いて宣言することにより、メソッド全体を try として扱うことができ、 catch はそのメソッドを使う側に委ねることができる。

public void hoge() throws HogeException{
    throw new HogeException();
}

チェック例外と非チェック例外

2018年時点での考え

基本的に RuntimeException 系の非チェック例外のサブクラスを用いて例外のフローを構築していくといいという考え。

java/basic/exception/start.txt · 最終更新: 2020-10-07 17:39 by ore