メソッドの呼び出し元を見つける必要があります。スタックトレースまたはリフレクションを使用することは可能ですか?
12 に答える
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
Javadocs によると:
配列の最後の要素は、スタックの一番下を表します。これは、シーケンス内で最も新しいメソッド呼び出しです。
AStackTraceElement
にはgetClassName()
、getFileName()
、getLineNumber()
およびがありgetMethodName()
ます。
実験して、必要なインデックスを決定する必要があります (おそらくstackTraceElements[1]
または[2]
)。
注: Java 9 以降を使用している場合は、 Ali Dehghani の回答StackWalker.getCallerClass()
で説明されているように使用する必要があります。
以下のさまざまな方法の比較は、歴史的な理由から興味深いものです。
代替ソリューションは、この機能強化のリクエストへのコメントで見つけることができます。getClassContext()
カスタムの方法を使っておりSecurityManager
、スタックトレース方式よりも速いようです。
次のプログラムは、提案されたさまざまなメソッドの速度をテストします (最も興味深い部分は内部クラスにありますSecurityManagerMethod
)。
/**
* Test the speed of various methods for getting the caller class name
*/
public class TestGetCallerClassName {
/**
* Abstract class for testing different methods of getting the caller class name
*/
private static abstract class GetCallerClassNameMethod {
public abstract String getCallerClassName(int callStackDepth);
public abstract String getMethodName();
}
/**
* Uses the internal Reflection class
*/
private static class ReflectionMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
}
public String getMethodName() {
return "Reflection";
}
}
/**
* Get a stack trace from the current thread
*/
private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
}
public String getMethodName() {
return "Current Thread StackTrace";
}
}
/**
* Get a stack trace from a new Throwable
*/
private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return new Throwable().getStackTrace()[callStackDepth].getClassName();
}
public String getMethodName() {
return "Throwable StackTrace";
}
}
/**
* Use the SecurityManager.getClassContext()
*/
private static class SecurityManagerMethod extends GetCallerClassNameMethod {
public String getCallerClassName(int callStackDepth) {
return mySecurityManager.getCallerClassName(callStackDepth);
}
public String getMethodName() {
return "SecurityManager";
}
/**
* A custom security manager that exposes the getClassContext() information
*/
static class MySecurityManager extends SecurityManager {
public String getCallerClassName(int callStackDepth) {
return getClassContext()[callStackDepth].getName();
}
}
private final static MySecurityManager mySecurityManager =
new MySecurityManager();
}
/**
* Test all four methods
*/
public static void main(String[] args) {
testMethod(new ReflectionMethod());
testMethod(new ThreadStackTraceMethod());
testMethod(new ThrowableStackTraceMethod());
testMethod(new SecurityManagerMethod());
}
private static void testMethod(GetCallerClassNameMethod method) {
long startTime = System.nanoTime();
String className = null;
for (int i = 0; i < 1000000; i++) {
className = method.getCallerClassName(2);
}
printElapsedTime(method.getMethodName(), startTime);
}
private static void printElapsedTime(String title, long startTime) {
System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
}
}
Java 1.6.0_17 を実行している 2.4 GHz Intel Core 2 Duo MacBook からの出力の例:
Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.
内部 Reflection メソッドは、他のメソッドよりもはるかに高速です。新しく作成された からスタック トレースを取得するThrowable
方が、現在の から取得するよりも高速ですThread
。そして、呼び出し元クラスを見つける非内部的な方法の中で、カスタムSecurityManager
が最も速いようです。
アップデート
lyomiがこのコメントで指摘しているように、このメソッドは Java 7 アップデート 40 でデフォルトで無効になり、Java 8 で完全に削除されました。これについて詳しくは、Java バグ データベース のこの問題を参照してください。sun.reflect.Reflection.getCallerClass()
更新 2
zammbiが発見したように、Oracleはsun.reflect.Reflection.getCallerClass()
. Java 8 でも引き続き使用できます (ただし、非推奨です)。
アップデート 3
3 年後: 現在の JVM とのタイミングに関する更新。
> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.
this
メソッドへの参照を渡さないようにしようとしているようです。渡すthis
ことは、現在のスタック トレースを介して呼び出し元を見つけるよりもはるかに優れています。 よりオブジェクト指向の設計へのリファクタリングはさらに優れています。 発信者を知る必要はありません。必要に応じてコールバック オブジェクトを渡します。
ワンライナー:
Thread.currentThread().getStackTrace()[2].getMethodName()
2 を 1 に置き換える必要がある場合があることに注意してください。
このメソッドは同じことを行いますが、もう少し単純でおそらくもう少しパフォーマンスが高く、リフレクションを使用している場合は、それらのフレームを自動的にスキップします。唯一の問題は、JRockit 1.4-->1.6 のランタイム クラスには含まれていますが、Sun 以外の JVM には存在しない可能性があることです。(ポイントは、パブリッククラスではありません)。
sun.reflect.Reflection
/** Returns the class of the method <code>realFramesToSkip</code>
frames up the stack (zero-based), ignoring frames associated
with java.lang.reflect.Method.invoke() and its implementation.
The first frame is that associated with this method, so
<code>getCallerClass(0)</code> returns the Class object for
sun.reflect.Reflection. Frames associated with
java.lang.reflect.Method.invoke() and its implementation are
completely ignored and do not count toward the number of "real"
frames skipped. */
public static native Class getCallerClass(int realFramesToSkip);
値がどうあるrealFramesToSkip
べきかについては、 の Sun 1.5 および 1.6 VM バージョンにはjava.lang.System
、 を呼び出す getCallerClass() と呼ばれるパッケージで保護されたメソッドがありますsun.reflect.Reflection.getCallerClass(3)
が、ヘルパー クラスの追加フレームがあるため、ヘルパー ユーティリティ クラスでは 4 を使用しました。呼び出し。
/**
* Get the method name for a depth in call stack. <br />
* Utility function
* @param depth depth in the call stack (0 means current method, 1 means call method, ...)
* @return method name
*/
public static String getMethodName(final int depth)
{
final StackTraceElement[] ste = new Throwable().getStackTrace();
//System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
return ste[ste.length - depth].getMethodName();
}
たとえば、デバッグ目的で呼び出しメソッド行を取得しようとする場合、これらの静的メソッドをコーディングする Utility クラスを通過する必要があります
(古い java1.4 コード、StackTraceElement の潜在的な使用法を示すためだけに)。
/**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
* From the Stack Trace.
* @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
*/
public static String getClassMethodLine()
{
return getClassMethodLine(null);
}
/**
* Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
*/
public static String getClassMethodLine(final Class aclass)
{
final StackTraceElement st = getCallingStackTraceElement(aclass);
final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
+")] <" + Thread.currentThread().getName() + ">: ";
return amsg;
}
/**
* Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
* Stored in array of the callstack. <br />
* Allows to get past a certain class.
* @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils.
* @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
* @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
*/
public static StackTraceElement getCallingStackTraceElement(final Class aclass)
{
final Throwable t = new Throwable();
final StackTraceElement[] ste = t.getStackTrace();
int index = 1;
final int limit = ste.length;
StackTraceElement st = ste[index];
String className = st.getClassName();
boolean aclassfound = false;
if(aclass == null)
{
aclassfound = true;
}
StackTraceElement resst = null;
while(index < limit)
{
if(shouldExamine(className, aclass) == true)
{
if(resst == null)
{
resst = st;
}
if(aclassfound == true)
{
final StackTraceElement ast = onClassfound(aclass, className, st);
if(ast != null)
{
resst = ast;
break;
}
}
else
{
if(aclass != null && aclass.getName().equals(className) == true)
{
aclassfound = true;
}
}
}
index = index + 1;
st = ste[index];
className = st.getClassName();
}
if(resst == null)
{
//Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies
throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
}
return resst;
}
static private boolean shouldExamine(String className, Class aclass)
{
final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
return res;
}
static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
{
StackTraceElement resst = null;
if(aclass != null && aclass.getName().equals(className) == false)
{
resst = st;
}
if(aclass == null)
{
resst = st;
}
return resst;
}
私は前にこれをしました。新しい例外を作成し、それをスローせずにスタックトレースを取得してから、スタックトレースを調べることができます。他の答えが言うように、それは非常に費用がかかります-タイトなループでそれをしないでください。
以前、パフォーマンスがそれほど重要ではないアプリのロギングユーティリティに対してこれを行いました(実際には、ボタンのクリックなどのアクションに結果をすばやく表示する限り、パフォーマンスはほとんど重要ではありません)。
スタックトレースを取得する前でしたが、例外には.printStackTrace()が含まれていたため、System.outを自分で作成したストリームにリダイレクトしてから(new Exception())。printStackTrace(); System.outをリダイレクトして、ストリームを解析します。楽しいもの。
private void parseExceptionContents(
final Exception exception,
final OutputStream out)
{
final StackTraceElement[] stackTrace = exception.getStackTrace();
int index = 0;
for (StackTraceElement element : stackTrace)
{
final String exceptionMsg =
"Exception thrown from " + element.getMethodName()
+ " in class " + element.getClassName() + " [on line number "
+ element.getLineNumber() + " of file " + element.getFileName() + "]";
try
{
out.write((headerLine + newLine).getBytes());
out.write((headerTitlePortion + index++ + newLine).getBytes() );
out.write((headerLine + newLine).getBytes());
out.write((exceptionMsg + newLine + newLine).getBytes());
out.write(
("Exception.toString: " + element.toString() + newLine).getBytes());
}
catch (IOException ioEx)
{
System.err.println(
"IOException encountered while trying to write "
+ "StackTraceElement data to provided OutputStream.\n"
+ ioEx.getMessage() );
}
}
}
以下は、このトピックで示したヒントに基づいて作成したコードの一部です。それが役に立てば幸い。
(このコードを改善するための提案をお気軽にお寄せください。教えてください)
カウンタ:
public class InstanceCount{
private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
private CounterInstanceLog counterInstanceLog;
public void count() {
counterInstanceLog= new counterInstanceLog();
if(counterInstanceLog.getIdHashCode() != 0){
try {
if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
}
counterInstanceLog.incrementCounter();
instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
}
(...)
}
そしてオブジェクト:
public class CounterInstanceLog{
private int idHashCode;
private StackTraceElement[] arrayStackTraceElements;
private int instanceCount;
private String callerClassName;
private StackTraceElement getProjectClasses(int depth) {
if(depth< 10){
getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
if(getCallerClassName().startsWith("com.yourproject.model")){
setStackTraceElements(Thread.currentThread().getStackTrace());
setIdHashCode();
return arrayStackTraceElements[depth];
}
//+2 because one new item are added to the stackflow
return getProjectClasses(profundidade+2);
}else{
return null;
}
}
private void setIdHashCode() {
if(getNomeClasse() != null){
this.idHashCode = (getCallerClassName()).hashCode();
}
}
public void incrementaContador() {
this.instanceCount++;
}
//getters and setters
(...)
}