从Java中的静态方法获取类名


244

如何从类的静态方法中获取类的名称。例如

public class MyClass {
    public static String getClassName() {
        String name = ????; // what goes here so the string "MyClass" is returned
        return name;
    }
}

为了将其放在上下文中,我实际上想在异常中将类名作为消息的一部分返回。


try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren

Answers:


231

为了支持正确的重构(重命名类),则应使用以下任一方法:

 MyClass.class.getName(); // full name with package

或(感谢@James Van Huis):

 MyClass.class.getSimpleName(); // class name and no more

136
如果您打算像这样对MyClass的知识进行硬编码,那么不妨只做String name =“ MyClass”; !
约翰·托普利2009年

111
但是,然后在IDE中重构类名称将无法正常工作。
James Van Huis

9
真正。尽管MyClass.class将确保不会因“更改类名称”重构而忘记这一行
工具箱2009年

19
希望 “ this”在静态上下文中起作用以表示Java中的当前Class,该“ class.xxx”在实例或静态代码中均被允许表示此类!问题在于,在上下文中,MyClass是冗长且冗长的。但是,尽管我喜欢Java,但它似乎确实倾向于冗长。
劳伦斯·多尔

51
如果我要在子类中调用静态方法并且想要子类名称怎么办?
爱德华·福尔克

120

按照工具包的说明进行操作。不要做这样的事情:

return new Object() { }.getClass().getEnclosingClass();

7
如果该类扩展了另一个类,则不会返回实际的类,而只会返回基类。
路易斯·索埃罗

1
@LuisSoeiro我相信它会返回定义该方法的类。我不确定基类是如何纳入静态上下文的。
汤姆·哈特芬

8
我不明白为什么getClass()不能是静态的。这样就不需要此“惯用语”。
mmirwaldt

1
@mmirwaldt getClass返回运行时类型,因此不能为静态。
汤姆·哈特芬

5
这是真正的答案。因为您不需要自己在代码中编写类的名称。
鲍勃2015年

99

在Java 7+中,您可以在静态方法/字段中执行此操作:

MethodHandles.lookup().lookupClass()

我要说Reflection.getCallerClass()。但是它给出了有关被包含在“ sun”包中的警告。因此,这可能是一个更好的解决方案。
Foumpie 2015年

1
@Foumpie:Java 9将引入一个正式的API,它将取代这个非正式的Reflection.getCallerClass()东西。对于他的微不足道的操作来说有点复杂,即Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());,但是当然,这与事实将更加强大。
Holger

6
这无疑是最好的解决方案。它避免了指定实际类名的需要,它并不是晦涩难懂,也不是hack,根据下面的Artyom Krivolapov的帖子,它也是迄今为止最快的方法。
Skomisa

@Rein如果在基类中调用了运行时类,有没有办法?
下滑

59

因此,当我们需要静态获取类对象或类的全名/简单名称而没有显式使用MyClass.class语法时,就会遇到这种情况。

在某些情况下可能非常方便,例如 高层函数(在这种情况下,kotlin创建了无法从kotlin代码访问的静态Java类)。

我们有一些不同的变体来获取此信息:

  1. new Object(){}.getClass().getEnclosingClass();
    Tom Hawtin 指出-大头针

  2. getClassContext()[0].getName();SecurityManager
    所指出克里斯托弗

  3. new Throwable().getStackTrace()[0].getClassName();
    路德维希伯爵

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    来自Keksi

  5. 最后
    MethodHandles.lookup().lookupClass();
    来自Rein


我准备了一个 所有变体的基准,结果是:

# Run complete. Total time: 00:04:18

Benchmark                                                      Mode  Cnt      Score     Error  Units
StaticClassLookup.MethodHandles_lookup_lookupClass             avgt   30      3.630 ±   0.024  ns/op
StaticClassLookup.AnonymousObject_getClass_enclosingClass      avgt   30    282.486 ±   1.980  ns/op
StaticClassLookup.SecurityManager_classContext_1               avgt   30    680.385 ±  21.665  ns/op
StaticClassLookup.Thread_currentThread_stackTrace_1_className  avgt   30  11179.460 ± 286.293  ns/op
StaticClassLookup.Throwable_stackTrace_0_className             avgt   30  10221.209 ± 176.847  ns/op


结论

  1. 使用的最佳变体,相当干净而且速度很快。
    自Java 7和Android API 26起可用!
 MethodHandles.lookup().lookupClass();
  1. 如果您需要Android或Java 6的此功能,则可以使用第二好的选择。它也相当快,但是在每个使用位置都创建了一个匿名类 :(
 new Object(){}.getClass().getEnclosingClass();
  1. 如果您在许多地方都需要它,又不希望由于大量匿名类而使您的字节码膨胀,那SecurityManager就是您的朋友(第三好的选择)。

    但是您不能随便打电话getClassContext()-它在SecurityManager课程中受到保护。您将需要一些这样的帮助器类:

 // Helper class
 public final class CallerClassGetter extends SecurityManager
 {
    private static final CallerClassGetter INSTANCE = new CallerClassGetter();
    private CallerClassGetter() {}

    public static Class<?> getCallerClass() {
        return INSTANCE.getClassContext()[1];
    }
 }

 // Usage example:
 class FooBar
 {
    static final Logger LOGGER = LoggerFactory.getLogger(CallerClassGetter.getCallerClass())
 }
  1. 您可能根本不需要基于getStackTrace()from异常或的最后两个变体Thread.currentThread()非常低效,只能将类名作为String,而不返回Class<*>实例。


聚苯乙烯

如果您想为静态kotlin utils(如我:)创建记录器实例,则可以使用以下帮助器:

import org.slf4j.Logger
import org.slf4j.LoggerFactory

// Should be inlined to get an actual class instead of the one where this helper declared
// Will work only since Java 7 and Android API 26!
@Suppress("NOTHING_TO_INLINE")
inline fun loggerFactoryStatic(): Logger
    = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())

用法示例:

private val LOGGER = loggerFactoryStatic()

/**
 * Returns a pseudo-random, uniformly distributed value between the
 * given least value (inclusive) and bound (exclusive).
 *
 * @param min the least value returned
 * @param max the upper bound (exclusive)
 *
 * @return the next value
 * @throws IllegalArgumentException if least greater than or equal to bound
 * @see java.util.concurrent.ThreadLocalRandom.nextDouble(double, double)
 */
fun Random.nextDouble(min: Double = .0, max: Double = 1.0): Double {
    if (min >= max) {
        if (min == max) return max
        LOGGER.warn("nextDouble: min $min > max $max")
        return min
    }
    return nextDouble() * (max - min) + min
}

38

该指令可以正常工作:

Thread.currentThread().getStackTrace()[1].getClassName();

5
请注意,它可能真的很慢。但是,您可以复制粘贴它。
的GaborLipták

1
这样做还有一个好处,就是不必每次使用对象或线程时都创建它。
Erel Segal-Halevi 2013年

5
@ErelSegalHalevi虽然它在后台创建了很多StackTraceElement:(
Navin

4
如果您查看源代码,Thread.getStackTrace()您会发现它除了return (new Exception()).getStackTrace();在上被调用外没有任何其他作用currentThread()。因此,@ count ludwig的解决方案是实现相同目标的更直接方法。
T公牛

34

通过使用JNI,您可以做一些非常可爱的事情:

MyObject.java:

public class MyObject
{
    static
    {
        System.loadLibrary( "classname" );
    }

    public static native String getClassName();

    public static void main( String[] args )
    {
        System.out.println( getClassName() );
    }
}

然后:

javac MyObject.java
javah -jni MyObject

然后:

MyObject.c:

#include "MyObject.h"

JNIEXPORT jstring JNICALL Java_MyObject_getClassName( JNIEnv *env, jclass cls )
{
    jclass javaLangClass = (*env)->FindClass( env, "java/lang/Class" );
    jmethodID getName = (*env)->GetMethodID( env, javaLangClass, "getName",
        "()Ljava/lang/String;" );
    return (*env)->CallObjectMethod( env, cls, getName );
}

然后将C编译到一个名为的共享库中libclassname.so并运行Java!

*咯咯笑


为什么这不是内置的?
ggb667 2014年

聪明,我很欣赏幽默。美中不足的是,C函数的默认名称Java_MyObject_getClassName已嵌入名称。解决方法是使用JNI RegisterNatives。当然,您必须将其与JNI配合使用FindClass(env, 'com/example/MyObject'),所以那里也没有胜利。
2014年

2
@Renate,所以整个答案实际上是在开玩笑吗?如果是这样,请明确指出,因为您知道,我们应该为他人提供帮助,所以不要让无辜者陷入陷阱。
斯特凡纳·古里科

好吧,这不是我的玩笑,我指出那是个玩笑。此方案的用例通常用于在日志中标识类。除去所有复杂性,通常可以归结为以下一种:private static final String TAG = "MyClass"private static final String TAG = MyClass.class.getSimpleName();第二种方法更适合使用IDE进行全局类重命名。
重现

20

我用它来初始化类顶部的Log4j Logger(或注释)。

PRO:Throwable已经加载,您可以通过不使用“ IO重载” SecurityManager来节省资源。

缺点:关于这是否适用于所有JVM的一些问题。

// Log4j . Logger --- Get class name in static context by creating an anonymous Throwable and 
// getting the top of its stack-trace. 
// NOTE you must use: getClassName() because getClass() just returns StackTraceElement.class 
static final Logger logger = Logger.getLogger(new Throwable() .getStackTrace()[0].getClassName()); 

:创建自己的异常类,以便在JVM不会打扰jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/...
4F2E4A2E

1
如果您要提出与此解决方案一样糟糕的建议,请至少将您的专业人士与使用MyClass.class.getName()的自然解决方案联系起来,而不是像滥用SecurityManager这样的可怕解决方案。
索伦Boisen

缺点:太冗长;速度慢(实际上是次要的一点,因为在加载类时它仅运行一次)。
toolforger

13

滥用SecurityManager

System.getSecurityManager().getClassContext()[0].getName();

或者,如果未设置,请使用扩展它的内部类(下面的示例从Real的HowTo卑鄙地复制了):

public static class CurrentClassGetter extends SecurityManager {
    public String getClassName() {
        return getClassContext()[1].getName(); 
    }
}

9

如果要使用它的完整程序包名称,请致电:

String name = MyClass.class.getCanonicalName();

如果只需要最后一个元素,请调用:

String name = MyClass.class.getSimpleName();

5

逐字使用呼叫方的类,例如 MyClass.class.getName()实际完成此工作,但是如果将此代码传播到需要该类名的多个类/子类中,则很容易复制/粘贴错误。

还有汤姆·霍顿的食谱实际上还不错,只需要以正确的方式烹饪即可:)

如果您有一个带有静态方法的基类,可以从子类中调用该静态方法,并且此静态方法需要知道实际的调用者的类,则可以通过以下方式实现:

class BaseClass {
  static sharedStaticMethod (String callerClassName, Object... otherArgs) {
    useCallerClassNameAsYouWish (callerClassName);
    // and direct use of 'new Object() { }.getClass().getEnclosingClass().getName()'
    // instead of 'callerClassName' is not going to help here,
    // as it returns "BaseClass"
  }
}

class SubClass1 extends BaseClass {
  static someSubclassStaticMethod () {
    // this call of the shared method is prone to copy/paste errors
    sharedStaticMethod (SubClass1.class.getName(),
                        other_arguments);
    // and this call is safe to copy/paste
    sharedStaticMethod (new Object() { }.getClass().getEnclosingClass().getName(),
                        other_arguments);
  }
}

4

一种重构安全,剪切和粘贴安全的解决方案,避免了以下临时类的定义。

编写一个静态方法来恢复类名,并小心地将类名包含在方法名中:

private static String getMyClassName(){
  return MyClass.class.getName();
}

然后以您的静态方法调用它:

public static void myMethod(){
  Tracer.debug(getMyClassName(), "message");
}

通过避免使用字符串来提供重构安全性,并允许剪切和粘贴安全性,因为如果剪切和粘贴调用方方法,您将在目标“ MyClass2”类中找不到getMyClassName(),因此您将不得不重新定义和更新它。


3

由于这个问题,是类似“ this.class”而不是“ ClassName.class”的?被标记为与此重复(这是有争议的,因为该问题是关于类而不是类名的),我在这里发布答案:

class MyService {
    private static Class thisClass = MyService.class;
    // or:
    //private static Class thisClass = new Object() { }.getClass().getEnclosingClass();
    ...
    static void startService(Context context) {
        Intent i = new Intent(context, thisClass);
        context.startService(i);
    }
}

定义thisClass私有很重要,因为:
1)不能继承:派生类必须定义自己的类,thisClass否则会产生错误消息
2)其他类的引用应采用ClassName.class而不是ClassName.thisClass

有了thisClass定义,访问类的名称变为:

thisClass.getName()

1

我需要多个类的静态方法中的类名,因此我使用以下方法实现了JavaUtil类:

public static String getClassName() {
    String className = Thread.currentThread().getStackTrace()[2].getClassName();
    int lastIndex = className.lastIndexOf('.');
    return className.substring(lastIndex + 1);
}

希望对您有所帮助!


1
不仅因为魔术数字2(使用此方法很容易导致NullPointerException)而使用它,还严重依赖于虚拟机的准确性。在方法的javadoc中:*在某些情况下,某些虚拟机可能会从堆栈跟踪中忽略一个或多个堆栈帧。在极端情况下,允许没有与此线程相关的堆栈跟踪信息的虚拟机从该方法返回零长度数组。*
猎枪

0

我在staticnon static场景中都使用了这两种方法:

主班:

//For non static approach
public AndroidLogger(Object classObject) {
    mClassName = classObject.getClass().getSimpleName();
}

//For static approach
public AndroidLogger(String className) {
    mClassName = className;
}

如何提供类别名称:

非静态方式:

private AndroidLogger mLogger = new AndroidLogger(this);

静态方式:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());

-1

如果使用反射,则可以获取Method对象,然后:

method.getDeclaringClass().getName()

要获取方法本身,可以使用:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)

3
您怎么知道什么是“类名”?:)
alfasin

1
Class.forName("class name")已经给你一个类。为什么要通过Method检索它?
谢尔盖·伊里索夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.