繁体   English   中英

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

[英]Getting the class name from a static method in Java

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

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

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

为了正确支持重构(重命名类),您应该使用:

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

或(感谢@James Van Huis ):

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

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

MethodHandles.lookup().lookupClass()

按照@toolkit 所说的去做。 不要做这样的事情:

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

编辑:或者,如果您使用的是在最初编写此答案后很好的 Java 版本,请使用@Rein 的答案。)

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

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

我们有几种不同的变体来获取此信息:

  1. new Object(){}.getClass().getEnclosingClass();
    汤姆·霍廷( Tom Hawtin)指出- tackline

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

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

  4. Thread.currentThread().getStackTrace()[1].getClassName();
    来自凯克西

  5. 最后真棒
    MethodHandles.lookup().lookupClass();
    缰绳


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

# 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()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
}

该指令工作正常:

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

你可以像这样使用 JNI 来做一些非常棒的事情:

我的对象.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

然后:

我的对象.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!

*轻笑

我使用它在我的类(或注释)顶部初始化 Log4j 记录器。

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

CON:关于这是否适用于所有 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()); 

滥用 SecurityManager

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

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

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

如果你想要整个包名,请调用:

String name = MyClass.class.getCanonicalName();

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

String name = MyClass.class.getSimpleName();

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);
  }
}

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

编写一个恢复类名的静态方法,注意在方法名中包含类名:

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

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

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

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

由于这个问题像 `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()

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

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

希望它会有所帮助!

我已经将这两种方法用于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());

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

method.getDeclaringClass().getName()

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM