java:例外発生後に例外のメッセージを書き変える

例外オブジェクトにはメッセージが格納されており、getMessage()メソッドでそのメッセージを取得することができます。
しかし、このメッセージはコンストラクタでのみ設定可能であり、メッセージを後で変更するメソッドは用意されていないため、例外クラスに用意されている手段では例外発生後にメッセージを書き変えることはできません。

しかし、リフレクションを使用することで、メッセージを後で書き変えることが可能です。
メッセージはThrowableクラスのprivateのクラス変数”detailMessage”に保持されるため、これをリフレクションで書き変えます。

サンプルコードは以下の通りです。

【サンプルコード】

・ExceptionTest.java

【実行結果】

・コンソール(標準出力)

・コンソール(標準エラー出力)


ちなみに、リフレクション時に発生してしまう標準エラー出力ですが、(試していませんが)Java8では出ないようです。
筆者の環境(Java10)だと出てしまいます。

そこで以下の2つの手段を試しましたが、何れもエラー抑止はできませんでした。

1.JVM引数で抑止

“–illegal-access=deny”を指定したら逆に異常終了するようになってしまいました。

2.標準エラー出力の出力ストリームを変更

通常、”System.setErr(自作PrintStreamオブジェクト);”を記述することで標準エラー出力の出力先を変更し、コンソールに出力されないようにすることができるのですが、今回のケースではそれができませんでした。
“System.err.close();”を記述した場合はコンソールへの出力を抑止することができたので、リフレクション時のWARNINGメッセージでは”System.err”を直接使用しているのではないかと思います。
なお、”System.err”はクローズしたらリオープンすることができないので、”System.err.close();”を実行してしまうと、以降は予期せぬ例外等が発生しても標準エラー出力として出力できなくなってしまいます。
(”System.err.close();”を実行した後に”System.setErr(System.out);”を実行して、予期せぬ例外等も全て標準出力として出力することならできますが…)


いかがでしたでしょうか。

今回の記事では、例外発生時のメッセージを後から書き変えられることを確認しましたが、標準エラー出力が出力されることを考えると、このようなソースコードはできる限り書くべきではないと思います。
どうしても書き変え可能な変数を伝播させたいのであれば、独自例外クラスに独自のクラス変数を定義してそれを伝播させる等の工夫をするべきだと思います。

java:例外を先に生成して後でthrowする

通常のjavaのソースコードでは、例外を発生させると同時にthrowしていると思います。
しかし、例外クラスもクラスの一つであり、newするとオブジェクトが生成されますので、先に例外クラスのオブジェクトを生成し、オブジェクトとしてやりとりした後、後でthrowすることが可能です。

以下、サンプルコードです。
FileNotFoundExceptionを生成した後にIOExceptionを生成・上書きし、IOExceptionを返すというサンプルコードです。

【サンプルコード】

・ExceptionTest.java

【実行結果】


いかがでしたでしょうか。

今回の記事では、先に例外クラスのオブジェクトを生成して後でthrowできることを確認しました。
しかし、通常、後でthrowするようなソースコードは書かないと思います。
少なくとも、今回のような例では、フラグ変数でどの例外を返すかの情報を保持し、後で例外オブジェクトを1回だけ生成するべきです(オブジェクトの生成にはリソースが必要なため)。

実務の中で後からthrowしたいという話が出たので、そもそもできるのかというのを確認したのですが、できたとしてもトリッキーな印象は否めないので、このようなソースコードを書くことには慎重になった方が良いと思います。

リスコフの置換原則とは

リスコフの置換原則とはオブジェクト指向の設計の原則の一つであり、「基本クラスをサブクラスに入れ替えても問題なく動かなけらばならない」という原則です。
もしサブクラスに入れ替えると正しく動かなくなるのであれば、クラスを利用する側はサブクラスを使用してはならないというのを意識しながらコーディングしなければならず、保守性が低下します。

クラスの入力・出力に目を向けると、以下の条件を満たすとリスコフの置換原則を満たすことができます。

1.サブクラスの入力のパターンは、基本クラスの入力のパターンよりも緩い

2.サブクラスの出力のパターンは、基本クラスの入力のパターンよりも厳しい
 

もし、サブクラスの入力のパターンが基本クラスの入力のパターンよりも厳しいと、利用する側はサブクラスを利用する際にサブクラスが許容する入力であるかどうかを意識する必要が出てきてしまいます。
また、サブクラスの出力のパターンが基本クラスの出力のパターンよりも緩いと、利用する側はサブクラスを利用する際にサブクラスが予期せぬ出力をしないか意識する必要が出てきてしまいます。

以下のサンプルコードは、割り算を行う基本クラスであるDivideクラスを、サブクラスのDivideBusinessクラス・DivideBad1クラス・DivideBad2クラスで入れ替えてみるサンプルです。

DivideBusinessクラスは、基本クラスが許容する入力パターンに加え分母=0のパターンも許容し、基本クラスが出力することがある負数を出力しません。Divideクラスと入れ替えても特に考慮するパターンが増えることはなく、リスコフの置換原則を満たした良いクラスです。
しかし、DivideBad1クラスは、基本クラスが許容する分子=負数のパターンを許容せず、基本クラスと入れ替えた場合は分子が負数になっていないか意識する必要が出てきてしまいます。
また、DivideBad2クラスは、基本クラスと異なりnullを出力するパターンがあり、基本クラスと入れ替えた場合は出力がnullになる場合を意識する必要が出てきてしまいます。

【サンプルコード】

・DivideMain.java

・Divide.java

・DivideBusiness.java

・DivideBad1.java

・DivideBad2.java

【結果】


いかがでしたでしょうか。

ポリモーフィズムを利用するとソースコードを簡潔にできるのですが、それを実現するためにはリスコフの置換原則を満たすことが必要です。
デザインパターン等で良い設計に触れている方なら自然に満たせるものかもしれませんが、今一度意識してみるのも良いかもしれません。

単一責任の原則とは

オブジェクト指向の設計ではいくつかの原則があります。
その中でも有名な原則の一つが「単一責任の原則」です。

この原則は、その名の通り「一つのクラスに持たせる責任は一つにする」という原則です。
この原則が何のために用いられるのかと言うと、複数の理由で1つのクラスを修正するのを防ぐためです。
ここで言う「理由」を「案件」、「クラス」を「ソース」と捉えると分かりやすいと思います。
複数の案件で1つのソースを修正する必要が出てしまうと、並行開発手順やバージョン管理等が煩雑になってしまい、品質に悪影響が及びます。

「一つのクラスに持たせる責任は一つにする」と捉えると具体的なイメージが浮かびにくいと思うのですが、「複数の案件で1つのソースを修正しないような粒度で設計する」と捉えると分かりやすいと思います。


以下では、javaのソースコードで例を出します。
商品の名前と値段を登録し、それを表示するという例です。

まずは悪い例からです。

【サンプルコード1】

・CommodityMain.java

・RegistShowCommodity.java

【実行結果1】


ここで、「商品の税込み価格を計算可能にする」という案件と、「表示を綺麗にする」という案件が発生したとします。
今の設計だと、どちらの案件も RegistShowCommodityクラスの修正となってしまいます。

そこで、商品の登録を行う RegistCommodityクラス、商品の価格計算を行う CalcCommodityクラス、価格表示を行う ShowCommodityクラスの3クラスに分割します。

【サンプルコード2】

・CommodityMain.java

・RegistCommodity.java

・CalcCommodity.java

・ShowCommodity.java

【実行結果2】


3クラスに分割した結果、「商品の税込み価格を計算可能にする」という案件は CalcCommodityクラスの修正、「表示を綺麗にする」という案件は ShowCommodityクラスの修正で対応できるようになり、別々の案件で1つのクラスを修正する状態を防ぐことができました。

【サンプルコード3】

・CommodityMain.java

・RegistCommodity.java

・CalcCommodity.java

・ShowCommodity.java

【実行結果3】


いかがでしたでしょうか。

オブジェクト指向の設計と言うと覚えることが多くて大変だという印象を持つかもしれませんが、いくつかの原則を抑えるだけでも良い設計ができます。
(デザインパターンも、原則に則って設計を考えると自ずと導き出されるものだと思っています)

単一責任の原則は代表的な原則の一つなので、覚えておいて損はないと思います。

クラス図とjavaソースの対応一覧

クラス図とはUMLの一種で、以下のような形でクラスの定義と各クラスの関係を表す設計図のことです。

今回は、クラス図で使われる記法とjavaソースの対応について、一覧にしてみました。
参考になれば幸いです。

【クラス定義】

【各クラスの関係】


いかがでしたでしょうか?

クラス図を書かなくともjavaのプログラムは作れてしまうので、情報を伝達したり整理したりするためにクラス図を書かなければならないとなった時に書き方を忘れてしまうことは意外とあると思います。
javaのプログラムに慣れた方であれば、日本語で説明されるよりもソースで説明された方がわかりやすいと思ったので、このような記事を書いてみました。