如何在Java中获取调用类的名称?

25

我希望在这个问题上得到一些帮助,

示例:

public class A {
    private void foo() {
        //Who invoked me?
    }
}

public class B extends A {}

public class C extends A {}

public class D {
     C.foo();
}

基本情况如下。我的问题是,方法foo()如何知道谁在调用它?

编辑:基本上我正在尝试做一个数据库层,在class A中,我将创建一个生成SQL语句的方法。这些语句是通过获取调用class的所有public属性的值来动态生成的。


4
一种根据调用者类别改变行为的方法真的是颠覆了面向对象编程。你如何测试这样的类,并使其在测试中与生产环境中的行为相同?一定有更好的实现方式... - Dafydd Rees
1
这让我想起了Fortran的COME FROM语句。http://www.fortran.com/come_from.html - Jim Ferrans
@Thorbjorn:已完成。(对不起,如果我不能正确地输入你名字中有趣的斯堪的纳维亚字符) - R. Martinho Fernandes
2
这段代码根本没有任何意义。 - duffymo
为什么不直接将类名传递给foo函数呢? - Cheng Thao
显示剩余2条评论
13个回答

38

最简单的方法如下:

String className = new Exception().getStackTrace()[1].getClassName();

但实际上除了一些记录日志的目的,这里不应该有这种需求,因为这是一个相当昂贵的任务。你认为这是解决什么问题的方法?我们可能会提出更好的建议。

编辑:你的评论如下:

基本上我正在尝试创建一个数据库层,在A类中将创建一个方法来生成SQL语句,这些语句通过获取调用类的所有公共属性的值动态生成。

我强烈建议您寻找现有的ORM库,例如HibernateiBatis或任何适合您口味的JPA实现


在构造函数中,异常是否会填充堆栈跟踪?还是只有在抛出异常时才会填充? - R. Martinho Fernandes
8
@马克:这真是糟糕的设计。我会深刻地重新考虑它。 - R. Martinho Fernandes
6
@Peter:Thread.currentThread().getStackTrace()[0].getMethodName() 的结果始终为 "getStackTrace"。你可以猜测出为什么... - R. Martinho Fernandes
1
@Mark,这会给你类的名称,但不是实例。换句话说,你将从哪个对象获取公共字段。你应该传入一个数据对象。 - Thorbjørn Ravn Andersen
3
@Peter:看一下Thread#getStackTrace()的源代码。嗯...它调用了new Exception().getStackTrace() - BalusC
显示剩余4条评论

27

Java 9:堆栈遍历API

JEP 259提供了一个高效的标准API用于堆栈遍历,允许轻松过滤和延迟访问堆栈跟踪中的信息。首先,您应该获取StackWalker的实例:

import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
// other imports

StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

之后您可以调用 getCallerClass() 方法:

Class<?> callerClass = walker.getCallerClass();
无论你如何配置StackWalker实例,getCallerClass方法都会忽略反射帧、隐藏帧以及与MethodHandle相关的帧。此外,该方法不应在第一个堆栈帧上调用。

16

也许根据你的使用情况,将调用者的类传递给方法会更有意义,例如:

public class A { public void foo(Class<?> c) { ... } }

然后像这样称呼它:

public class B { new A().foo(getClass() /* or: B.class */ ); }

3
感谢您指出正确的做法,让我们不要因为这样的事情弄乱堆栈跟踪。 +1 - R. Martinho Fernandes
2
是的。如果调用者必须使用反射来执行任务,则让链接清晰明了。传递类或实例。 - CPerkins
总的来说,我同意你的观点,但如果你正在创建某种框架,它可能会变得非常有用。 - mfeingold
1
+1,使用堆栈跟踪的唯一原因是在调试场景中。 - Yishai
1
你为什么要谈论一个类参数呢?他需要类型为D的对象,这样他才能读取属性值。我认为马克混淆了类/对象,或者那里的一切都是静态的吗? - mhaller
显示剩余2条评论

3

foo()是私有的,因此调用者始终在A类中。


D类不会编译通过。 - BalusC

2
如果您使用SLF4J作为应用程序的日志记录系统,可以使用以下内容:
Class<?> source = org.slf4j.helpers.Util.getCallingClass();

我认为这比new Exception().getStackTrace()更快,因为getStackTrace()总是在克隆堆栈跟踪。


2
我会使用StackWalker
private static Class<?> getCallingClass(int skip) {
    StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
    Optional<? extends Class<?>> caller = walker.walk(frames ->
            frames.skip(skip).findFirst().map(StackWalker.StackFrame::getDeclaringClass)
    );
    return caller.get();
}

如果你需要调用方法的类,请使用 skip=1

0
一个hacky的解决方案是使用sun.reflect.Reflection.getCallerClass
public void foo() {
    Class<?> caller = sun.reflect.Reflection.getCallerClass();
    // ...
}

这种方法很hacky,因为你必须确保调用Reflection.getCallerClass()的类已经被引导类加载器加载,并且注解@CallerSensitivegetCallerClass带有该注解)才能正常工作。因此,除非你的项目恰好使用Java代理将你的类添加到引导类加载器搜索中,否则它可能不是最佳解决方案。


另一个需要注意的事情是,包 sun.reflect 显示它是一个不安全的内部 API,在未来的版本中可能不再可用。理想情况下,应该避免依赖这些包中的类。 - geisterfurz007


0
我只是回答这个问题,因为不知道为什么上面的答案开始涉及异常处理 - 原始问题与异常无关。
所以,不是试图确定调用该方法的方法,而是为了提供有关为其派生类生成SQL语句的基类创建更多信息,这里是一个面向对象的解决方案...
  1. 使基类抽象并包括返回构建sql语句所需数据的抽象方法。

  2. 这将包括方法,例如...

    getColumnList() getFromTable() getJoinedTables() getFilterColumns()

  3. 然后,基类不关心谁在调用它,因为它要调用派生类来获取创建SQL语句所需的所有细节。 基类知道派生类将提供这些方法的实现,因为它们是抽象的。

另一种实现方式是拥有一个SQLGenerator类,它接收一个具有上述方法的接口,并通过这些方法传递给它的实例进行操作。 为此,你需要将上述抽象方法移动到接口中,所有与SQL相关的类都将实现该接口。

列表项


0
使用以下代码,您可以获取生成调用堆栈的第一个类:
    public String getInvonkingClassName(boolean fullClassNameNeeded){

        StackTraceElement[] stack = new Exception().getStackTrace();
        String className = stack[stack.length-1].getClassName();


        if(!fullClassNameNeeded){
            int idx = className.lastIndexOf('.');
            className = className.substring(idx+1);
        }

        return className;
    }

布尔型参数用于获取包括包名在内的完整类名,或仅获取类名。


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