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

244

¿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 la clase como parte de un mensaje en una excepción.

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

Respuestas:

231

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
kit de herramientas
fuente
136
Si va a codificar con conocimiento de MyClass de esa manera, entonces también podría hacer String name = "MyClass"; !
John Topley
111
Pero luego, refactorizar el nombre de la clase en su IDE no funcionará correctamente.
James Van Huis
99
Cierto. Aunque MyClass.class se asegurará de que esta línea no se olvide con una refactorización de 'cambio de nombre de clase'
toolkit
19
¡ Desearía que "esto" funcionara en un contexto estático para significar la Clase actual en Java, que se permitiera que "class.xxx" en cualquier instancia o código estático significara esta clase! El problema con esto es que MyClass es detallado y redundante, en el contexto. Pero, por mucho que me guste Java, parece inclinarse hacia la verbosidad.
Lawrence Dol
51
¿Qué sucede si estoy llamando al método estático en una subclase y quiero el nombre de la subclase?
Edward Falk
120

Haz lo que dice el juego de herramientas. No hagas nada como esto:

return new Object() { }.getClass().getEnclosingClass();
Tom Hawtin - tackline
fuente
77
Si la clase extiende otra, esto no devuelve la clase real, solo la clase base.
Luis Soeiro
1
@LuisSoeiro Creo que devuelve la clase en la que se define el método. No estoy seguro de cómo la clase base tiene en cuenta el contexto estático.
Tom Hawtin - tackline
8
No entiendo por qué getClass () no puede ser estático. Este "idioma" no sería necesario.
mmirwaldt
1
@mmirwaldt getClassdevuelve el tipo de tiempo de ejecución, por lo que no puede ser estático.
Tom Hawtin - tackline el
55
Esta es la verdadera respuesta. Porque no necesita escribir el nombre de su clase en su código usted mismo.
Bobs
99

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

MethodHandles.lookup().lookupClass()
Rienda
fuente
Iba a decir Reflection.getCallerClass(). Pero da una advertencia sobre estar en los paquetes 'sol'. Entonces esta podría ser una mejor solución.
Foumpie
1
@Foumpie: Java 9 introducirá una API oficial que reemplazará a esta Reflection.getCallerClass()cosa no oficial . Es un poco complicado para su operación trivial, es decir Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, pero por supuesto, eso está relacionado con el hecho de que será mucho más poderoso.
Holger
66
Esta es fácilmente la mejor solución. Evita la necesidad de especificar el nombre real de la clase, no es oscuro, no es un truco y, según la publicación de Artyom Krivolapov a continuación, también es, con mucho, el enfoque más rápido.
skomisa
@Rein ¿Hay alguna forma de obtener la clase de tiempo de ejecución si se llamó en la clase base?
Glide
59

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

Puede ser realmente útil en algunos casos, por ejemplo, la instancia del registrador para funciones de nivel superior (en este caso, kotlin crea una clase Java estática no accesible 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 - tackline

  2. getClassContext()[0].getName();de lo 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 impresionante
    MethodHandles.lookup().lookupClass();
    de Rein


He preparado un Los puntos de referencia para todas las variantes y 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 código de bytes se hinche debido a toneladas de clases anónimas, SecurityManageres su amigo (la tercera mejor opción).

    Pero no puedes simplemente llamar getClassContext(), está protegido en la SecurityManagerclase. Necesitarás una clase auxiliar 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 no necesite usar las dos últimas variantes en función de la getStackTrace()excepción from o de Thread.currentThread(). Muy ineficiente y puede devolver solo el nombre de la clase como a String, no la Class<*>instancia.


PD

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

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
}
Artyom Krivolapov
fuente
38

Esta instrucción funciona bien:

Thread.currentThread().getStackTrace()[1].getClassName();
Keksi
fuente
55
Tenga cuidado, que puede ser muy lento. Sin embargo, puedes copiarlo y pegarlo.
Gábor Lipták
1
Esto tiene el beneficio adicional de no tener que crear un Objeto o un Hilo cada vez que lo usa.
Erel Segal-Halevi
55
@ErelSegalHalevi Sin embargo, crea un montón de StackTraceElement s en segundo plano :(
Navin
44
Si miras el código fuente de Thread.getStackTrace(), verás que no hace nada más que return (new Exception()).getStackTrace();en el caso de ser llamado en el currentThread(). Entonces, la solución de @count ludwig es la forma más directa de lograr lo mismo.
T-Bull
34

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

MyObject.java:

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

    public static native String getClassName();

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

luego:

javac MyObject.java
javah -jni MyObject

luego:

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 compile el C en una biblioteca compartida llamada libclassname.so y ejecute Java.

*risita

vida útil
fuente
¿Por qué esto no está incorporado?
ggb667
Inteligente, y aprecio el humor. La mosca en la pomada es que el nombre predeterminado de la función C Java_MyObject_getClassNametiene el nombre incrustado. La forma de evitar eso es usar el JNI RegisterNatives. Por supuesto, tendrías que alimentar eso con el JNI FindClass(env, 'com/example/MyObject'), así que tampoco ganarás allí.
Renate
2
@Renate, ¿entonces toda esta respuesta es en realidad una broma? Si es así, explíquelo de manera explícita porque sabe que se supone que debemos ayudar a otras personas, así que no empujemos a los inocentes a las trampas.
Stéphane Gourichon
Bueno, no era mi broma y señalé que era una broma. El caso de uso para este escenario es generalmente para identificar la clase en los registros. Eliminando toda la complejidad, generalmente se reduce a hacer: private static final String TAG = "MyClass"o private static final String TAG = MyClass.class.getSimpleName();El segundo es más amigable para el cambio de nombre de clase global usando un IDE.
Renate
20

Lo uso para iniciar el 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()); 
Conde Ludwig
fuente
Cree su propia clase de excepción para que el jvm no se moleste: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E
1
Si va a sugerir algo tan horrible como esta solución, al menos relacione a sus profesionales con la solución natural de usar MyClass.class.getName (), en lugar de otra solución horrible como abusar del SecurityManager.
Søren Boisen
Más contras: demasiado detallado; lento (esto es realmente un punto menor porque se ejecuta solo una vez, cuando se carga la clase).
toolforger
13

Abusar del SecurityManager

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

O, si no está configurado, use una clase interna que lo extienda (ejemplo a continuación, copiado vergonzosamente de Real's HowTo ):

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

Si desea el nombre completo del paquete, llame al:

String name = MyClass.class.getCanonicalName();

Si solo desea el último elemento, llame a:

String name = MyClass.class.getSimpleName();
James Van Huis
fuente
5

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

Y la receta de Tom Hawtin, de hecho, no está mal, solo hay que cocinarla de la manera correcta :)

En caso de que tenga una clase base con un método estático al que se pueda llamar 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);
  }
}
Sergey Ushakov
fuente
4

Una solución segura para refactorizar, cortar y pegar 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 teniendo cuidado de 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 proporciona evitando el uso de cadenas, se garantiza la seguridad de cortar y pegar porque si corta y pega el método de llamada no encontrará el getMyClassName () en la clase de destino "MyClass2", por lo que se verá obligado a redefinirlo y actualizarlo.

avalori
fuente
3

¿Desde la pregunta Algo como `this.class` en lugar de` ClassName.class`? está marcado como un duplicado para este (lo cual 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 definirlo thisClasscomo privado porque:
1) no debe heredarse: las clases derivadas deben definir las suyas propias thisClasso generar un mensaje de error
2) las referencias de otras clases deben hacerse en ClassName.classlugar de hacerlo ClassName.thisClass.

Con thisClassdefinido, el acceso al nombre de la clase se convierte en:

thisClass.getName()
18446744073709551615
fuente
1

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!

phaderer
fuente
1
No solo es malo usar esto debido al número mágico 2 (que podría resultar fácilmente en una NullPointerException), sino que también depende en gran medida de la precisión de la máquina virtual. Del javadoc del método: * Algunas máquinas virtuales pueden, en algunas circunstancias, omitir uno o más marcos de pila de la traza de pila. En el caso extremo, una máquina virtual que no tiene información de seguimiento de pila relacionada con este subproceso puede devolver una matriz de longitud cero de este método. *
Shotgun
0

He usado estos dos enfoques para ambos staticynon static escenario:

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 la clase:

forma no estática:

private AndroidLogger mLogger = new AndroidLogger(this);

Manera estática:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
0xAliHn
fuente
-1

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

method.getDeclaringClass().getName()

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

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)
Franzé Jr.
fuente
3
¿Y cómo sabrá qué es: "nombre de clase"? :)
alfasin
1
Después de Class.forName("class name")darte una clase. ¿Por qué quieres recuperarlo a través de Method?
Sergey Irisov