可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How can one get the name of the class from a static method in that class. For example
public class MyClass {
public static String getClassName() {
String name = ????; // what goes here so the string "MyClass" is returned
return name;
}
}
To put it in context, I actually want to return the class name as part of a message in an exception.
回答1:
In order to support refactoring correctly (rename class), then you should use either:
MyClass.class.getName(); // full name with package
or (thanks to @James Van Huis):
MyClass.class.getSimpleName(); // class name and no more
回答2:
Do what toolkit says. Do not do anything like this:
return new Object() { }.getClass().getEnclosingClass();
回答3:
In Java 7+ you can do this in static method/fields:
MethodHandles.lookup().lookupClass()
回答4:
So, we have a situation when we need to statically get class object or a class full/simple name without an explicit usage of MyClass.class
syntax.
It can be really handy in some cases, e.g. logger instance for the kotlin upper-level functions (in this case kotlin creates a static Java class not accessible from the kotlin code).
We have a few different variants for getting this info:
new Object(){}.getClass().getEnclosingClass();
noted by Tom Hawtin - tackline
getClassContext()[0].getName();
from the SecurityManager
noted by Christoffer
new Throwable().getStackTrace()[0].getClassName();
by count ludwig
Thread.currentThread().getStackTrace()[1].getClassName();
from Keksi
and finally awesome
MethodHandles.lookup().lookupClass();
from Rein
I've prepared a jmh benchmark for all variants and results are:
# 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
Conclusions
- Best variant to use, rather clean and monstrously fast.
Available only since Java 7 and Android API 26!
MethodHandles.lookup().lookupClass();
- In case you need this functionality for Android or Java 6, you can use the second best variant. It's rather fast too, but creates an anonymous class in each place of usage :(
new Object(){}.getClass().getEnclosingClass();
If you need it in many places and don't want your bytecode to bloat due to tons of anonymous classes – SecurityManager
is your friend (third best option).
But you can't just call getClassContext()
– it's protected in the SecurityManager
class. You will need some helper class like this:
// 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())
}
- You probably don't ever need to use last two variants based on the
getStackTrace()
from exception or the Thread.currentThread()
. Very inefficient and can return only the class name as a String
, not the Class<*>
instance.
P.S.
If you want to create a logger instance for static kotlin utils (like me :), you can use this helper:
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())
Usage example:
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
}
回答5:
This instruction works fine:
Thread.currentThread().getStackTrace()[1].getClassName();
回答6:
You could do something really sweet by using JNI like this:
MyObject.java:
public class MyObject
{
static
{
System.loadLibrary( "classname" );
}
public static native String getClassName();
public static void main( String[] args )
{
System.out.println( getClassName() );
}
}
then:
javac MyObject.java
javah -jni MyObject
then:
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 );
}
Then compile the C up into a shared library called libclassname.so
and run the java!
*chuckle
回答7:
I use this to init the Log4j Logger at the top of my classes (or annotate).
PRO: Throwable is already loaded and you might save resources by not using the "IO heavy" SecurityManager.
CON: Some question as to whether this will work for all JVMs.
// 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());
回答8:
Abuse the SecurityManager
System.getSecurityManager().getClassContext()[0].getName();
Or, if not set, use an inner class that extends it (example below shamefully copied from Real's HowTo):
public static class CurrentClassGetter extends SecurityManager {
public String getClassName() {
return getClassContext()[1].getName();
}
}
回答9:
If you want the entire package name with it, call:
String name = MyClass.class.getCanonicalName();
If you only want the last element, call:
String name = MyClass.class.getSimpleName();
回答10:
Verbatim use of caller's class like MyClass.class.getName()
actually does the job, but is prone to copy/paste errors if you propagate this code to numerous classes/subclasses where you need this class name.
And Tom Hawtin's recipe is in fact not bad, one just needs to cook it the right way :)
In case you have a base class with a static method that may be called from subclasses, and this static method needs to know the actual caller's class, this may be achieved like the following:
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);
}
}
回答11:
A refactoring-safe, cut&paste-safe solution that avoids the definition of ad-hoc classes below.
Write a static method that recover the class name having care to include the class name in the method name:
private static String getMyClassName(){
return MyClass.class.getName();
}
then recall it in your static method:
public static void myMethod(){
Tracer.debug(getMyClassName(), "message");
}
Refactoring safety is given by avoiding the use of strings, cut&paste safety is granted because if you cut&paste the caller method you won't find the getMyClassName() in the target "MyClass2" class, so you will be forced to redefine and update it.
回答12:
Since the question Something like `this.class` instead of `ClassName.class`? is marked as a duplicate for this one (which is arguable because that question is about the class rather than class name), I'm posting the answer here:
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);
}
}
It is important to define thisClass
as private because:
1) it must not be inherited: derived classes must either define their own thisClass
or produce an error message
2) references from other classes should be done as ClassName.class
rather than ClassName.thisClass
.
With thisClass
defined, access to the class name becomes:
thisClass.getName()
回答13:
I needed the class name in the static methods of multiple classes so I implemented a JavaUtil Class with the following method :
public static String getClassName() {
String className = Thread.currentThread().getStackTrace()[2].getClassName();
int lastIndex = className.lastIndexOf('.');
return className.substring(lastIndex + 1);
}
Hope it will help !
回答14:
I have used these two approach for both static
and non static
scenario:
Main class:
//For non static approach
public AndroidLogger(Object classObject) {
mClassName = classObject.getClass().getSimpleName();
}
//For static approach
public AndroidLogger(String className) {
mClassName = className;
}
How to provide class name:
non static way:
private AndroidLogger mLogger = new AndroidLogger(this);
Static way:
private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
回答15:
If you are using reflection, you can get the Method object and then:
method.getDeclaringClass().getName()
To get the Method itself, you can probably use:
Class<?> c = Class.forName("class name");
Method method = c.getDeclaredMethod ("method name", parameterTypes)