こんばんは。
平穏無事な日々を送りたいわたしです。
でも、世の中には思いがけないことも多いものです。
そんなわけで今日は例外処理です。
例外の比較
一般的な例外(Javaでは非検査例外)
説明 | C# | Java |
---|---|---|
null値の参照型変数を参照しようとした | NullReferenceException | NullPointerException |
境界外のインデックスを使用して配列、 コレクションなどの要素にアクセスしようとした |
IndexOutOfRangeException ※1 | IndexOutOfBoundsException ├ArrayIndexOutOfBoundsException └StringIndexOutOfBoundsException |
算術計算で例外的条件が発生した | ArithmeticException ├DivideByZeroException ├NotFiniteNumberException └OverflowException |
ArithmeticException |
不正な引数、または不適切な引数をメソッドに渡した | ArgumentException ├ArgumentNullException └ArgumentOutOfRangeException |
IllegalArgumentException |
無効なキャストが行われた | InvalidCastException | ClassCastException |
オブジェクトの現在の状態に対して、不正または不適切なときにメソッドが呼び出された | InvalidOperationException | IllegalStateException |
文字列を数値型に変換しようとしたとき、文字列の形式が正しくない ※2 | FormatException | NumberFormatException ※3 |
要求されたオペレーションがサポートされていない | NotSupportedException | UnsupportedOperationException |
間違った型の要素を配列に格納しようとした | ArrayTypeMismatchException | ArrayStoreException |
リソースが欠落している | MissingManifestResourceException | MissingResourceException |
※1 String.RemoveなどはArgumentOutOfRangeExceptionになる
※2 FormatExceptionの範囲はもう少し広く、「引数の書式が仕様に一致していない」ことを表す
※3 NumberFormatExceptionはIllegalArgumentExceptionのサブクラス
JavaではErrorとして扱われるもの
説明 | C# | Java |
---|---|---|
入れ子になったメソッド呼び出しが多くなりすぎ、 実行スタックがオーバーフローした |
StackOverflowException | StackOverflowError |
プログラムの実行を継続するためのメモリが不足している | OutOfMemoryException | OutOfMemoryError |
C#にはあるけどJavaにはなさそうなもの
説明 | C# |
---|---|
特定のプラットフォームで機能が実行されない | PlatformNotSupportException |
Dispose済みのオブジェクトで操作が実行される | ObjectDisposedException |
要求されたメソッドまたは操作が実装されない ※4 | NotImplementedException |
コレクションに該当するキーが無い ※5 | KeyNotFoundException |
※4 Javaではテンプレートでの自動挿入時にはUnsupportedOperationExceptionを使う
※5 Javaではキーがない場合にはnullを返す
検査例外と非検査例外
C#には非検査例外がありません。というかJavaには検査例外があります。
この一言で終わってしまう気もするのですが、せっかくなのでもう少し踏み込んでみます。
まず先に言っておくと、Java以降に誕生した主要な言語に検査例外を持つものはほとんどありません。
というかわたしはJavaしか知らないのですが、他にもあるんでしょうか。
非検査例外のメリット
非検査例外の良いところはコンパイル時点でどのような例外が発生するかを想定できることです。
つまり、メソッドを呼び出すときなどに
「いいですか、私を呼んだら例外出るかもしれませんよ。覚悟してくださいね。」
と呼び出す相手に脅してお知らせしてくれるわけです。
これは単にプログラマの心構えの問題だけではなくて、
例外のハンドリングの責任を誰が持つか、というのを明確にすることができます。
つまり、発生した例外を自分で処理できる場合にはその例外の存在を上の階層に意識させる必要がないですし、
処理しきれない場合にはthrows節を使って上の階層に明確に例外発生の可能性を通知することができるわけです。
非検査例外のデメリット
当然メリットの裏返しがデメリットにもなるわけで、
メソッドが投げる例外を想定できないということが挙げられます。
これだけであれば設計思想とか趣味の範囲で済むのかもしれませんが、
致命的なのがシグネチャの変更がメソッドの互換性とか拡張性を著しく下げる場合があるということです。
例えば、検査例外であるSampleExceptionを投げるmethod3の例外処理を、二階層上のmethod1で行う場合を考えます。
void method1() { try { method2(); } catch (SampleException e) { System.out.println("例外だよ"); } } void method2() throws SampleException { method3(); } void method3() throws SampleException { throw new SampleException(); }
method2を書く際にはSampleExceptionはこの階層では処理しきれないので上に渡そうと考えるわけですが、
method3が投げる例外の種類が増えた場合、method2もthrows節を変更しなければなりません。
しかし、method2には実質的には何も変更はないのです。
void method1() { try { method2(); } catch (SampleException e) { System.out.println("例外だよ"); } catch (SampleException2 e) { System.out.println("例外2だよ"); } } void method2() throws SampleException, SampleException2 { method3(); } void method3() throws SampleException, SampleException2 { if (Math.random() >= 0.5) { throw new SampleException(); } else { throw new SampleException2(); } }
ネストがもっと深ければ、軽微な変更でもかなりの修正コストを要することになります。
これが面倒で複数の例外をラップして投げたりすることによって、
結果的にはどんな例外が飛んでくるのかよくわからなくなったりするのです。
もういっそ上がってくる例外をそのまま上に流すよって意味で新しい文法作ったらいいんじゃないでしょうか。
throwsに似せた感じでthroughとかどうです?
void method2() through {
method3();
}
もちろんIDEでちゃんとチェックしてくださいね。