例外オブジェクトにはメッセージが格納されており、getMessage()メソッドでそのメッセージを取得することができます。
しかし、このメッセージはコンストラクタでのみ設定可能であり、メッセージを後で変更するメソッドは用意されていないため、例外クラスに用意されている手段では例外発生後にメッセージを書き変えることはできません。
しかし、リフレクションを使用することで、メッセージを後で書き変えることが可能です。
メッセージはThrowableクラスのprivateのクラス変数”detailMessage”に保持されるため、これをリフレクションで書き変えます。
サンプルコードは以下の通りです。
【サンプルコード】
・ExceptionTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import java.io.IOException; import java.lang.reflect.Field; public class ExceptionTest { public static void main(String[] args) { try { method(); } catch (IOException e) { System.out.println(e.getMessage()); } catch (Exception e) { e.printStackTrace(); } } static void method() throws Exception { try { // メッセージ付きで例外を発生させる throw new IOException("hoge"); } catch (Exception e) { // detailMessageフィールドの定義を取得 Field fieldDefinition = Throwable.class.getDeclaredField("detailMessage"); // フィールドをアクセス可能に設定 fieldDefinition.setAccessible(true); // 例外オブジェクトのメッセージを変更する fieldDefinition.set(e, e.getMessage() + "fuga"); // 例外を再スロー throw e; } } } |
【実行結果】
・コンソール(標準出力)
1 |
hogefuga |
・コンソール(標準エラー出力)
1 2 3 4 5 |
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by ExceptionTest (file:/C:/pleiades/workspace/Hello/build/classes/) to field java.lang.Throwable.detailMessage WARNING: Please consider reporting this to the maintainers of ExceptionTest WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release |
ちなみに、リフレクション時に発生してしまう標準エラー出力ですが、(試していませんが)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);”を実行して、予期せぬ例外等も全て標準出力として出力することならできますが…)
いかがでしたでしょうか。
今回の記事では、例外発生時のメッセージを後から書き変えられることを確認しましたが、標準エラー出力が出力されることを考えると、このようなソースコードはできる限り書くべきではないと思います。
どうしても書き変え可能な変数を伝播させたいのであれば、独自例外クラスに独自のクラス変数を定義してそれを伝播させる等の工夫をするべきだと思います。
コメント