Java: メソッドの呼び出し元を調べる方法

メソッドが「どこから呼ばれたか」を調べる方法についてのメモ。

Throwable#getStackTrace()

Java 1.4 で、java.lang.Throwable クラスに getStackTrace() メソッドが追加された。これを呼ぶことで、例外スタックトレースの内容を取得することができる。
返されるのは java.lang.StackTraceElement[] で、0 番目が現在実行中のメソッド自身、1 番目が呼び出し元のメソッドを示す。例えば、次のようにして、呼び出し元のクラス名とメソッド名を出力することができる。

StackTraceElement e = new Exception().getStackTrace()[1];
System.out.println(e.getClassName() + "," + e.getMethodName());

ただし、いくつか制限事項もある。

  • JVM の実装によっては、getStackTrace() が長さ 0 の配列を返すことがある。
  • 呼び出し元のクラス名やメソッド名は得られるが、Class や Method といったオブジェクトは取得できない。したがって、クラスローダをまたいで呼び出し元の状態を調べるようなことはできない。
  • 例外の生成はやや重い処理なので、頻繁に使用すると速度低下を招く。

Throwable#printStackTrace()

ログ出力ライブラリである Log4j では、Java 1.3 まででも「呼び出し元メソッド名をログに出力する」という機能を使うことができた。
どうやっているかというと、printStackTrace() が出力する内容を解析するという力技。JVMスタックトレースを返さないと対応できない点は getStackTrace() と同じだが、一旦出力して再度解析して、というステップを踏むため、処理としてはさらに重い。
初めて聞いた時は「その手があったのか!」と驚いたものだ。

JNI

詳しく知らないが、メソッドを native 実装すれば呼び出し元の取得ができるらしい。
この手を Java 1.1 時代から使っている例が、java.util.ResourceBundle の getBundle() メソッドである。このメソッドにリソースバンドルの基底名を渡すと、呼び出し元クラスと同じクラスローダを使ってリソースバンドルの検索を行う。
ソースを見るとわかることだが、ResourceBundle には private な native メソッドがあり、それを使って呼び出し元を調べている。native メソッドを実装しなければならないということ自体が割と辛いが、Throwable を使う方式と違ってクラスローダをまたげるのが大きな強みだろう。