从Java中的静态方法获取类名
如何从这个类的静态方法获得类的名字。 例如
public class MyClass { public static String getClassName() { String name = ????; // what goes here so the string "MyClass" is returned return name; } }
为了把它放在上下文中,我实际上希望返回类名作为exception中消息的一部分。
为了正确支持重构(重命名类),那么你应该使用:
MyClass.class.getName(); // full name with package
或者(感谢@James Van Huis ):
MyClass.class.getSimpleName(); // class name and no more
做什么工具包说。 不要做这样的事情:
return new Object() { }.getClass().getEnclosingClass();
在Java 7 +中,您可以在静态方法/字段中执行此操作:
MethodHandles.lookup().lookupClass()
这个指令工作正常:
Thread.currentThread().getStackTrace()[1].getClassName();
你可以像这样使用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
为libclassname.so
的共享库并运行java!
*轻笑
我用它来初始化我的类(或注释)顶部的Log4jlogging器。
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); } }
所以,当我们需要静态地获取类对象或类的全名/简单名称而没有显式使用MyClass.class
语法时,我们遇到了这种情况。
在某些情况下它可以非常方便,比如logging器实例 kotlin高级函数(在这种情况下,kotlin创build一个静态java类,不能从kotlin代码访问)。
我们有几个不同的变种获取这个信息:
-
new Object(){}.getClass().getEnclosingClass();
汤姆·霍金 ( Tom Hawtin)指出 -
getClassContext()[0].getName();
从SecurityManager
Christoffer指出 -
new Throwable().getStackTrace()[0].getClassName();
由路德维格 -
Thread.currentThread().getStackTrace()[1].getClassName();
来自Keksi -
最后真棒
MethodHandles.lookup().lookupClass();
来自Rein
我已经准备好了所有变体的jmh基准,结果是:
# 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_javaClass_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
结论
- 最好的变种使用 ,而不是干净和怪异的快速。
仅适用于Java 7和Android API 26!
MethodHandles.lookup().lookupClass();
- 如果你需要Android或Java 6的这个function,你可以使用第二个最好的变种。 它也相当快, 但在每个使用地点创build一个一致的类 🙁
new Object(){}.getClass().getEnclosingClass();
-
如果你在很多地方需要它,并且不希望你的字节码由于数十个匿名类而膨胀 –
SecurityManager
是你的朋友(第三最佳select)。但是,您不能只调用
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()) }
- 您可能不需要使用基于exception
getStackTrace()
或Thread.currentThread()
最后两个变体。 非常低效,只能返回类名作为String
,而不是Class<*>
实例。
PS
如果你想为静态kotlin utils(像我:)创build一个logging器实例,你可以使用这个帮助器:
import org.slf4j.Logger import org.slf4j.LoggerFactory // Should be inlined to get an actual class instead of the one when 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 }
避免定义下面特定类的重构安全,剪切和粘贴安全的解决scheme。
编写一个静态方法来恢复类名,注意在方法名中包含类名:
private static String getMyClassName(){ return MyClass.class.getName(); }
然后在你的静态方法中回想一下:
public static void myMethod(){ Tracer.debug(getMyClassName(), "message"); }
通过避免使用string来实现重构安全性,因为如果剪切并粘贴调用方法,就不会在目标“MyClass2”类中findgetMyClassName(),所以您将被迫重新定义和更新它。
由于这个问题像`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)它不能被inheritance:派生类必须定义自己的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); }
希望它会帮助!
如果您正在使用reflection,您可以获得Method对象,然后:
method.getDeclaringClass().getName()
为了获得方法本身,你可以使用:
Class<?> c = Class.forName("class name"); Method method = c.getDeclaredMethod ("method name", parameterTypes)