「Java でマサカリ投げてみた」の解説

JJUG CCC 2015 Fall の懇親会の LT で発表し、おかげさまで大好評をいただきました「Java でマサカリ投げてみた!」。

しかし、かなり早口で、しかも説明を端折っていたため、発表をご覧いただいた方に疑問を抱かせてしまいました。

  • Masakari は本当に投げられたのか?
  • UnhandledExceptionHandler が無ければ AbstractMethodError が発生する。つまり、本当は AbstractMethodError が投げられているのではないか?*1

そこで、この記事では Java で Masakari を投げたときに何が起きているのかを詳しく解説します!


すみません、、、先に断っておくとLTほど面白くないです… (−−;)

Masakari クラスは本当にスローされたのか?

Masakari クラスが本当にスローされたか、eclipse などのIDE(デバッガ)で確認できます。


ただ、IDEでいつも通りにデバッグしようとしてもコンパイルエラーになってしまいます。
そのため、スライドにある手順でコンパイル&実行して、それをIDEの "リモートデバッグ" という機能を使ってデバッグする必要があります。
詳しい手順は、GitHub の Masakari4j のページに記載していますので、こちらをご確認ください。
ソースコード全文も、併せてコミットしています)


この手順で、"Unhandled Masakari" を出力している行でブレークポイントを張って実行してみると、このように arg1 (つまり、引数の Throwable e ) に Masakari オブジェクトが格納されているのが確認できます。


もちろん、 Throwable e = new Masakari(); というのは明らかに不正な代入です。
しかし、-noverify オプションで実行しているので、ノーチェックで代入が行われてしまうようです。
おそらく、本来であればこのような不正な処理はクラスロード時に Verifier がチェックしてエラーにしているので、実行時にはチェックしないのではないかと思います。*2

AbstractMethodError の原因

今回のプログラムは UnhandledExceptionHandler を入れて、独自にメッセージを出すようにしています。
しかし、これを削除してプログラムを実行*3すると、以下のエラーが発生します。

Exception in thread "main"
Exception: java.lang.AbstractMethodError thrown from the UncaughtExceptionHandler in thread "main"

この AbstractMethodError とはいったい何なのでしょうか。


これを先ほどと同じ手順でデバッグすると、以下の場所(ThreadGroup#uncaughtException 内で printStackTrace メソッドを呼び出したところ)で発生していることが分かりました*4


つまり、これは以下の事象が起こっているということになります。

  1. Throwable クラスのつもりで printStackTrace メソッドを呼び出そうとした
  2. しかし、実際は Masakari クラスだったため、そのメソッドがない
  3. そのため、AbstractMethodError がスローされた


確認したところ、このような挙動について AbstractMethodError の Java Doc に記載がありました。

アプリケーションが抽象メソッドを呼び出そうとした場合にスローされます。通常、このエラーはコンパイラによって検出されます。現在実行中のメソッドを最後にコンパイルしたあとで、あるクラスの定義が変更されて互換性が失われた場合にだけ、実行時にこのエラーが発生します。

AbstractMethodError (Java Platform SE 8)

まとめ

ということで、Java でマサカリは本当に投げられる、というお話でした。
今思うと、"Masakari" ってハードコーディングするのではなく e.getClass().getSimpleName() としておけばよかったですね…。


もし、ほかに疑問点がありましたら、コメントいただければと思います。

あ、もちろんマサカリは受け付けていませんよ(><)

*1:これはその場で「LT」として提示されました。これ、とても嬉しかったです。ありがとうございました!

*2:実行時に型チェックすると遅くなるので、クラスロード時に安全であることが保証できるのであればチェックしないようになっていると予想しています。

*3:もちろん、-noverify を付けて実行します。

*4:このメソッド内でステップオーバーなどすると、いきなり終了することがあります。そのため、該当箇所にブレークポイントを張って実行するのが確実です。