que - metodos y variables static java
Obteniendo el nombre de la clase de un método estático en Java (15)
Abusar del SecurityManager
System.getSecurityManager().getClassContext()[0].getName();
O, si no está configurado, use una clase interna que lo extienda (ejemplo abajo copiado vergonzosamente de Real''s HowTo ):
public static class CurrentClassGetter extends SecurityManager {
public String getClassName() {
return getClassContext()[1].getName();
}
}
¿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, realmente quiero devolver el nombre de la clase como parte de un mensaje en una excepción.
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 definir esta thisClass
como privada porque:
1) no debe ser heredado: las clases derivadas deben definir su propia thisClass
o generar un mensaje de error
2) las referencias de otras clases deben hacerse como ClassName.class
lugar de ClassName.thisClass
.
Con esta thisClass
definida, el acceso al nombre de la clase se convierte en:
thisClass.getName()
El uso MyClass.class.getName()
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 en realidad no es mala, solo hay que cocinarla de la manera correcta :)
En caso de que tenga una clase base con un método estático que pueda llamarse desde subclases, y este método estático necesite conocer la clase del llamante real, 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);
}
}
En Java 7+ puedes hacer esto en campos / métodos estáticos:
MethodHandles.lookup().lookupClass()
Esta instrucción funciona bien:
Thread.currentThread().getStackTrace()[1].getClassName();
Haz lo que dice el kit de herramientas. No hagas nada como esto:
return new Object() { }.getClass().getEnclosingClass();
He usado estos dos enfoques para escenarios tanto static
como 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 la clase:
forma no estática:
private AndroidLogger mLogger = new AndroidLogger(this);
Forma estática:
private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
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!
Para admitir la refactorización correctamente (cambiar el nombre de la clase), entonces debe usar:
MyClass.class.getName(); // full name with package
o (gracias a @James Van Huis ):
MyClass.class.getSimpleName(); // class name and no more
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, compile la C en una biblioteca compartida llamada libclassname.so
y ejecute el java!
*risita
Por lo tanto, tenemos una situación en la que necesitamos obtener de forma estática un objeto de clase o un nombre completo / simple de clase sin un uso explícito de la sintaxis MyClass.class
Puede ser muy útil en algunos casos, por ejemplo, la instancia del registrador para el kotlin kotlin de nivel superior de kotlin (en este caso, Kotlin crea una clase 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:
new Object(){}.getClass().getEnclosingClass();
observado por Tom Hawtin - tackleadogetClassContext()[0].getName();
desde elSecurityManager
observado por Christoffernew Throwable().getStackTrace()[0].getClassName();
por el conde ludwigThread.currentThread().getStackTrace()[1].getClassName();
de Keksiy finalmente impresionante
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
- La mejor variante de uso , bastante limpia y monstruosamente rápida.
Disponible solo desde Java 7 y Android API 26!
MethodHandles.lookup().lookupClass();
- 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();
Si lo necesita en muchos lugares y no quiere que su código de bytes se hinche debido a toneladas de clases anónimas,
SecurityManager
es su amigo (tercera mejor opción).Pero no puedes simplemente llamar a
getClassContext()
, está protegido en la claseSecurityManager
. Necesitarás una 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())
}
- Probablemente nunca necesite usar las dos últimas variantes basadas en
getStackTrace()
de la excepción oThread.currentThread()
. Muy ineficiente y puede devolver solo el nombre de la clase como unaString
, no la instancia deClass<*>
.
PD
Si desea crear una instancia de registrador para utils kotlin estáticos (como yo :), puede usar este asistente:
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
}
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 puedas usar:
Class<?> c = Class.forName("class name");
Method method = c.getDeclaredMethod ("method name", parameterTypes)
Si quieres el nombre completo del paquete, llama:
String name = MyClass.class.getCanonicalName();
Si solo quieres el último elemento, llama:
String name = MyClass.class.getSimpleName();
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();
}
entonces recuérdalo en tu método estático:
public static void myMethod(){
Tracer.debug(getMyClassName(), "message");
}
La seguridad de refactorización se da evitando el uso de cadenas, la seguridad de cortar y pegar se otorga porque si corta y pega el método del llamador no encontrará getMyClassName () en la clase de destino "MyClass2", por lo que se verá obligado a redefinirlo y actualizarlo.
Utilizo esto 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: Algunos cuestionan 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());