Obtener el nombre de clase de un método estático en Java

¿Cómo se puede obtener el nombre de la clase de un método estático en esa clase? Por ejemplo

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

Para ponerlo en contexto, en realidad quiero devolver el nombre de clase como parte de un mensaje en una excepción.

Para admitir la refactorización correctamente (cambiar el nombre de la clase), debe usar:

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

o (gracias a @James Van Huis ):

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

Haz lo que dice el toolkit. No hagas algo como esto:

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

En Java 7+ puedes hacer esto en métodos / campos estáticos:

 MethodHandles.lookup().lookupClass() 

Esta instrucción funciona bien:

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

Podrías hacer algo realmente dulce usando JNI así:

MyObject.java:

 public class MyObject { static { System.loadLibrary( "classname" ); } public static native String getClassName(); public static void main( String[] args ) { System.out.println( getClassName() ); } } 

entonces:

 javac MyObject.java javah -jni MyObject 

entonces:

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

Luego comstack la C en una biblioteca compartida llamada libclassname.so y ejecuta el java!

*risita

Entonces, tenemos una situación en la que necesitamos obtener estáticamente un objeto de clase o un nombre de clase completo / simple sin un uso explícito de la syntax de MyClass.class .

Puede ser realmente útil en algunos casos, por ejemplo, instancia de registrador para el funciones de nivel superior de kotlin (en este caso, kotlin crea una clase de Java estática a la que no se puede acceder desde el código de kotlin).

Tenemos algunas variantes diferentes para obtener esta información:

  1. new Object(){}.getClass().getEnclosingClass();
    señalado por Tom Hawtin – tachado

  2. getClassContext()[0].getName(); del SecurityManager
    señalado por Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    por el conde ludwig

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

  5. y finalmente increíble
    MethodHandles.lookup().lookupClass();
    de Rein

He preparado un punto de referencia jmh para todas las variantes y los resultados son:

 # 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 

Conclusiones

  1. La mejor variante para usar , bastante limpia y monstruosamente rápida.
    ¡Disponible solo desde Java 7 y Android API 26!
  MethodHandles.lookup().lookupClass(); 
  1. En caso de que necesite esta funcionalidad para Android o Java 6, puede usar la segunda mejor variante. También es bastante rápido, pero crea una clase anónima en cada lugar de uso 🙁
  new Object(){}.getClass().getEnclosingClass(); 
  1. Si lo necesita en muchos lugares y no quiere que su bytecode se hinche debido a toneladas de clases anónimas, SecurityManager es su amigo (la tercera mejor opción).

    Pero no puedes simplemente llamar a getClassContext() – está protegido en la clase SecurityManager . Necesitarás alguna clase de ayuda como esta:

  // 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. Probablemente nunca necesite usar las dos últimas variantes basadas en getStackTrace() de exception o Thread.currentThread() . Muy ineficiente y puede devolver solo el nombre de la clase como una String , no la Class<*> .

PD

Si desea crear una instancia de registrador para utilidades estáticas de kotlin (como yo :), puede usar esta ayuda:

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

Ejemplo de uso:

 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 } 

Lo uso para iniciar Log4j Logger en la parte superior de mis clases (o anotar).

PRO: Throwable ya está cargado y puede ahorrar recursos al no usar el SecurityManager “IO heavy”.

CON: Algunas preguntas sobre si esto funcionará para todas las 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()); 

Abusar del SecurityManager

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

O, si no está configurado, usa una clase interna que lo amplíe (ejemplo debajo vergonzosamente copiado del Real’s HowTo ):

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

Si desea el nombre completo del paquete con él, llame a:

 String name = MyClass.class.getCanonicalName(); 

Si solo quieres el último elemento, llama:

 String name = MyClass.class.getSimpleName(); 

El uso literal de la clase de la persona que llama como MyClass.class.getName() realmente hace el trabajo, pero es propenso a copiar y pegar errores si propaga este código a numerosas clases / subclases donde necesita este nombre de clase.

Y la receta de Tom Hawtin no es mala, solo se debe cocinar de la manera correcta 🙂

En caso de que tenga una clase base con un método estático que pueda invocarse desde subclases, y este método estático necesita conocer la clase real del llamante, esto se puede lograr de la siguiente manera:

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

Una solución segura para refactorización, de corte y pegado que evita la definición de clases ad-hoc a continuación.

Escriba un método estático que recupere el nombre de la clase con cuidado para incluir el nombre de la clase en el nombre del método:

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

luego recuérdalo en tu método estático:

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

La seguridad de refactorización se obtiene al evitar el uso de cadenas, la seguridad de cortar y pegar se concede porque si corta y pega el método de llamador no encontrará el nombre getMyClassName () en la clase meta “MyClass2”, por lo que se verá obligado a redefinirlo y actualizarlo.

Desde la pregunta Algo como `this.class` en lugar de` ClassName.class`? está marcado como duplicado para este (lo que es discutible porque esa pregunta es sobre la clase en lugar del nombre de la clase), estoy publicando la respuesta aquí:

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

Es importante definir thisClass como privado porque:
1) no debe ser heredado: las clases derivadas deben definir su propia esta thisClass o producir un mensaje de error
2) las referencias de otras clases se deben hacer como ClassName.class lugar de ClassName.thisClass .

Con thisClass definido, el acceso al nombre de clase se convierte en:

 thisClass.getName() 

Necesitaba el nombre de la clase en los métodos estáticos de varias clases, así que implementé una clase JavaUtil con el siguiente método:

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

Espero que ayude!

He utilizado estos dos enfoques para escenarios static y non static :

Clase principal:

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

Cómo proporcionar el nombre de clase:

forma no estática:

 private AndroidLogger mLogger = new AndroidLogger(this); 

Forma estática:

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

Si está utilizando la reflexión, puede obtener el objeto Método y luego:

 method.getDeclaringClass().getName() 

Para obtener el Método en sí mismo, probablemente puedas usar:

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