例外オブジェクトにはメッセージが格納されており、getMessage()メソッドでそのメッセージを取得することができます。
しかし、このメッセージはコンストラクタでのみ設定可能であり、メッセージを後で変更するメソッドは用意されていないため、例外クラスに用意されている手段では例外発生後にメッセージを書き変えることはできません。
しかし、リフレクションを使用することで、メッセージを後で書き変えることが可能です。
メッセージはThrowableクラスのprivateのクラス変数"detailMessage"に保持されるため、これをリフレクションで書き変えます。
サンプルコードは以下の通りです。
【サンプルコード】
・ExceptionTest.java
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;
}
}
}
【実行結果】
・コンソール(標準出力)
hogefuga
・コンソール(標準エラー出力)
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
----
ちなみに、リフレクション時に発生してしまう標準エラー出力ですが、筆者の環境(Java10)では抑止する良い手段が見つかりませんでした。
以下の2つの手段を試しています。
1.JVM引数で抑止
"--illegal-access=deny"を指定したら逆に異常終了するようになってしまいました。
下記ページによると、Java11も不可、Java8は可(そもそもリフレクション時の標準エラー出力が出ない)だそうです。
Java - Javaの開発環境を作る過程でのエラー文について|teratail
https://teratail.com/questions/153595
2.標準エラー出力の出力ストリームを変更
通常、"System.setErr(自作PrintStreamオブジェクト);"を記述することで標準エラー出力の出力先を変更し、コンソールに出力されないようにすることができるのですが、今回のケースではそれができませんでした。
"System.err.close();"を記述した場合はコンソールへの出力を抑止することができたので、リフレクション時のWARNINGメッセージでは"System.err"を直接使用しているのではないかと思います。
なお、"System.err"はクローズしたらリオープンすることができないので、"System.err.close();"を実行してしまうと、以降は予期せぬ例外等が発生しても標準エラー出力として出力できなくなってしまいます。
("System.setErr(System.out);"で標準出力として出力することならできますが…)