获取当前正在执行的方法的名称

539

有没有一种方法在Java中获取当前执行方法的名称?


1
我认为现在(自从Java 9)应该接受具有StackWalker的答案。您很久以前接受的答案现在已经过时。 - Honza Zidek
1
我认为现在(自从Java 9)应该接受使用StackWalker的答案。你很久以前接受的答案现在已经过时了。 - undefined
1
请参阅这个答案中的现代 java.lang.StackWalker 解决方案:链接 - Basil Bourque
24个回答

360

从技术上讲,这样做是可行的...

String name = new Object(){}.getClass().getEnclosingMethod().getName();

然而,在编译时会创建一个新的匿名内部类(例如YourClass$1.class)。因此,这将为每个使用该技巧的方法创建一个.class文件。此外,每次调用期间都会创建一个未使用的对象实例。因此,这可能是一种可接受的调试技巧,但它确实带来了显着的开销。

这种技巧的优点是getEnclosingMethod()返回java.lang.reflect.Method,可以用于检索方法的所有其他信息,包括注释和参数名称。这使得可以区分具有相同名称的特定方法(方法重载)。

请注意,根据getEnclosingMethod()的JavaDoc,这种技巧不应抛出SecurityException,因为内部类应使用相同的类加载器进行加载。因此,即使存在安全管理器,也无需检查访问条件。

请注意:必须使用getEnclosingConstructor()来获取构造函数。在(命名)方法之外的块中,getEnclosingMethod()返回null


10
这不会给你当前正在执行的方法。这将给你在其中定义了一个匿名/本地类的方法。-http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getEnclosingMethod() - shrini1000
7
class Local {}; String name = Local.class.getEnclosingMethod().getName();翻译: class Local {}; String name = Local.class.getEnclosingMethod().getName();意思是获取包含当前局部类的方法的名称。 - alexsmail
26
@shrini1000 的想法是在需要信息时使用这个片段,而不是将它放在一个库例程中。 - Thorbjørn Ravn Andersen
4
谢谢你的提示!不需要创建新对象,只需使用this.getClass().getEnclosingMethod().getName()即可。 - Lilo
4
@Lilo 错误了。getEnclosingMethod 获取的是类所定义的方法的名称。this.getClass() 对你没有帮助。@wutzebaer 你为什么需要这样做呢?你已经可以访问它们了。 - Hazel T
显示剩余7条评论

203

Thread.currentThread().getStackTrace()通常会包含您正在调用它的方法,但是有坑(请参见Javadoc):

某些虚拟机在某些情况下可能会从堆栈跟踪中省略一个或多个堆栈帧。 在极端情况下,允许没有关于此线程的堆栈跟踪信息的虚拟机从该方法返回零长度数组。


7
异常堆栈跟踪是否存在同样的陷阱? - Nate Parsons
8
是的。Throwable的文档中包含了完全相同的段落,介绍了getStackTrace()方法。 - Bombe
4
核心问题是 JVM 并不是“必须”提供堆栈跟踪,但大量工作已经投入到使 HotSpot 变得非常可靠。然而,如果您希望代码不依赖于特定 JVM 的行为,则需要知道这一点。 - Thorbjørn Ravn Andersen
Alexsmail的版本不会创建堆栈跟踪,并且可以让您访问实际的方法对象,而不仅仅是名称(因此您还可以找到返回类型)。我没有进行基准测试,但我怀疑他的方法速度要快得多,因为堆栈跟踪往往很昂贵。 - Gus

142

2009年1月:
完整的代码应该是(考虑到@Bombe的警告):

/**
 * 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 = Thread.currentThread().getStackTrace();

  //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
  // return ste[ste.length - depth].getMethodName();  //Wrong, fails for depth = 0
  return ste[ste.length - 1 - depth].getMethodName(); //Thank you Tom Tresansky
}

更多内容请参见此问题

2011年12月更新:

bluish评论说:

我使用JRE 6,它给我提供了错误的方法名称。
如果我写ste[2 + depth].getMethodName().就可以了。

  • 0getStackTrace()
  • 1getMethodName(int depth),而
  • 2是调用方法。

virgo47答案(得到了赞同)实际上计算出了正确的索引,以获取方法名称。


2
对我来说只有 "main"。:-/ - Prof. Falken
@Amigable:你尝试打印所有的StackTraceElement数组以进行调试并查看'main'是否是正确的方法了吗? - VonC
7
我使用JRE 6版本,但是它给出了错误的方法名。如果我写成 ste[2 + depth].getMethodName() 就可以正常工作。其中0代表getStackTrace(),1代表getMethodName(int depth),2代表调用方法。参见@virgo47答案 - bluish
2
@bluish:说得好。我已经在我的回答中包含了你的评论和对virgo47答案的引用。 - VonC
@VonC 这个实现真的正确吗?如果我们要允许深度=0,那么这里的深度必须是ste.length + 1才能给出当前方法。难道不应该是ste[depth + 1]吗? - mjs
显示剩余3条评论

92
我们使用这段代码来减少栈跟踪索引的潜在变化 - 现在只需调用methodName util即可。
public class MethodNameTest {
    private static final int CLIENT_CODE_STACK_INDEX;

    static {
        // Finds out the index of "this code" in the returned stack trace - funny but it differs in JDK 1.5 and 1.6
        int i = 0;
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            i++;
            if (ste.getClassName().equals(MethodNameTest.class.getName())) {
                break;
            }
        }
        CLIENT_CODE_STACK_INDEX = i;
    }

    public static void main(String[] args) {
        System.out.println("methodName() = " + methodName());
        System.out.println("CLIENT_CODE_STACK_INDEX = " + CLIENT_CODE_STACK_INDEX);
    }

    public static String methodName() {
        return Thread.currentThread().getStackTrace()[CLIENT_CODE_STACK_INDEX].getMethodName();
    }
}

看起来过于复杂,但我们在JDK 1.5中有一些固定的数字,当我们转移到JDK 1.6时有点惊讶它发生了变化。现在在Java 6/7中是一样的,但你永远不知道。这并不能证明在运行时该索引不会发生变化-但希望HotSpot表现不错。 :-)


2
这仍然微妙地依赖于供应商。JVM不一定能为此代码提供可靠的数据。 - Thorbjørn Ravn Andersen
7
根据JVM规范,JVM不必提供完整的堆栈跟踪(优化、内联等),你已经发现你的启发式方法在Oracle Java 5和Oracle Java 6之间发生了变化。没有任何保证其他JVM会按照您代码中所期望的那样运行,因此您在微妙地依赖于特定于供应商的行为。只要您意识到这一点,这是完全可以的,但如果您需要在IBM JVM(我们必须这样做)或Zing实例上部署,您可能需要重新审视您的启发式方法。 - Thorbjørn Ravn Andersen
1
这似乎是所有提供的选项中最健壮的,尽管存在依赖关系。 - Ian

48
 public class SomeClass {
   public void foo(){
      class Local {};
      String name = Local.class.getEnclosingMethod().getName();
   }
 }

名称将具有值foo。


6
Local.class.getEnclosingMethod() 返回 null。jdk1.6.0_31 和 play 1.2.5。 - eigil
@eigil 这很有趣,但是没有更多的信息很难说出什么出了问题或者我们应该什么时候期望 null - Maarten Bodewes
这是与此答案相同的技巧。它的优点是不会创建虚假的对象实例,缺点是需要一个类声明,无法内联在语句中(即通常需要额外的一行代码)。 - Maarten Bodewes
@eigil 你是在类内定义了一个类(例如SomeClass),还是在方法内定义了一个类(例如foo)?我发现如果没有被包含在方法或构造函数中,定义子类将导致getEnclosingMethod()返回null。 - D.N.
我非常确定我按照这个答案所描述的完全做了。我认为这可能与playframework有关。在“正常”的Java中测试没有任何问题。 - eigil

48

这两个选项都对我在Java中的工作有效:

new Object(){}.getClass().getEnclosingMethod().getName()
或者:
Thread.currentThread().getStackTrace()[1].getMethodName()

1
对于静态方法,请使用:<Class> .class.getEnclosingMethod().getName() - jellobird
根据Bombe的回答和javadoc的指示,要小心空数组。一些JVM可能不会填充堆栈跟踪数组。 - el-teedee

43
Java 9+中的

java.lang.StackWalker

自Java 9以来,可以使用StackWalker来完成此操作。

public static String getCurrentMethodName() {
    return StackWalker.getInstance()
                      .walk(s -> s.skip(1).findFirst())
                      .get()
                      .getMethodName();
}

public static String getCallerMethodName() {
    return StackWalker.getInstance()
                      .walk(s -> s.skip(2).findFirst())
                      .get()
                      .getMethodName();
}

StackWalker被设计为懒加载,因此比如Thread.getStackTrace更高效,后者会立即创建整个调用堆栈的数组。还可以查看JEP获取更多信息。


3
现在应该是被接受的答案。 - undefined
2
现在应该是被接受的答案。 - Honza Zidek
对我来说没问题,但是使用skip(0) - 用于当前的方法。 - undefined

35

我发现最快的方法是:

import java.lang.reflect.Method;

public class TraceHelper {
    // save it static to have it available on every call
    private static Method m;

    static {
        try {
            m = Throwable.class.getDeclaredMethod("getStackTraceElement",
                    int.class);
            m.setAccessible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String getMethodName(final int depth) {
        try {
            StackTraceElement element = (StackTraceElement) m.invoke(
                    new Throwable(), depth + 1);
            return element.getMethodName();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
它直接访问本地方法getStackTraceElement(int depth)。并将可访问的方法存储在静态变量中。

3
最快是指性能方面吗?有什么微基准测试来支持这种说法吗? - Ibrahim Arief
10
在1.6版本中使用简单的计时循环,在执行1,000,000次迭代时,使用这种方法需要1219毫秒,而使用new Throwable().getStackTrace()则需要5614毫秒。 - ach
1
m.setAccessible(true); 应该被 AccessController.doPrivileged 包围。这是需要考虑的事情,但不是硬性规定。 - avanderw
6
测试于2016年,至今仍是最快的。像@ach一样,我使用了100万次迭代。1.7_79:1.6秒对比15.2秒,1.8_74:1.8秒对比16.0秒。顺便说一下,我的基准测试中数组的长度为23,但无论堆栈深度如何,这种方法都能保持快速。 - Ryan
在Java 11中,这个不起作用:没有Throwable.getStackTraceElement(int)方法。 - Dennie
1
@Dennie 即使有这个方法,setAccessible(true)现在也会失败。 - Holger

24

使用以下代码:

    StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
    StackTraceElement e = stacktrace[1];//coz 0th will be getStackTrace so 1st
    String methodName = e.getMethodName();
    System.out.println(methodName);

2
这会为我打印出“getStackTrace” - 我正在使用Java 1.5 - Zack Macomber
根据Bombe的回答和javadoc的指示,要小心空数组。一些JVM可能不会填充堆栈跟踪数组。 - el-teedee

20
public static String getCurrentMethodName() {
        return Thread.currentThread().getStackTrace()[2].getClassName() + "." + Thread.currentThread().getStackTrace()[2].getMethodName();
    }

是的,目前最好的方法是将其转化为一个函数并获取跟踪中的第三个[2]帧(或称为如何调用)。 - mike rodent
1
不要重复自己,尤其是当重复的表达式非常昂贵时,比如 Thread.currentThread().getStackTrace()。这就是本地变量的作用,用于存储评估结果,以便您可以多次使用该结果而不是重复评估。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接