Comment peut-on obtenir le nom de la classe à partir d'une méthode statique dans cette classe. Par exemple
public class MyClass {
public static String getClassName() {
String name = ????; // what goes here so the string "MyClass" is returned
return name;
}
}
Pour le mettre en contexte, je souhaite en fait renvoyer le nom de la classe dans le cadre d’un message d’une exception.
Afin de prendre en charge correctement le refactoring (renommer la classe), vous devez utiliser soit:
MyClass.class.getName(); // full name with package
ou (grâce à @ James Van Huis ):
MyClass.class.getSimpleName(); // class name and no more
Faites ce que dit la boîte à outils. Ne faites rien comme ça:
return new Object() { }.getClass().getEnclosingClass();
Dans Java 7+, vous pouvez le faire avec des méthodes/champs statiques:
MethodHandles.lookup().lookupClass()
Nous sommes donc dans une situation où nous devons obtenir statiquement un objet de classe ou un nom de classe complet/simple sans utilisation explicite de la syntaxe MyClass.class
.
Cela peut être très pratique dans certains cas, par exemple. instance de journalisation pour le kotlin fonctions de niveau supérieur (dans ce cas, kotlin crée une classe statique Java qui n'est pas accessible à partir du code kotlin).
Nous avons plusieurs variantes pour obtenir cette information:
new Object(){}.getClass().getEnclosingClass();
noté par Tom Hawtin - tackline
getClassContext()[0].getName();
de la SecurityManager
noté par Christoffer
new Throwable().getStackTrace()[0].getClassName();
by count ludwig
Thread.currentThread().getStackTrace()[1].getClassName();
de Keksi
et enfin génialMethodHandles.lookup().lookupClass();
de Rein
J'ai préparé un repère jmh pour toutes les variantes et les résultats sont les suivants:
# 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
MethodHandles.lookup().lookupClass();
new Object(){}.getClass().getEnclosingClass();
Si vous en avez besoin dans de nombreux endroits et que vous ne voulez pas que votre bytecode gonfle à cause de tonnes de classes anonymes - SecurityManager
est votre ami (troisième meilleure option).
Mais vous ne pouvez pas simplement appeler getClassContext()
- il est protégé dans la classe SecurityManager
. Vous aurez besoin d'une classe d'aide comme celle-ci:
// 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())
}
getStackTrace()
from exception ou Thread.currentThread()
. Très inefficace et ne peut renvoyer que le nom de la classe sous la forme d'une instance String
, pas l'instance Class<*>
.Si vous voulez créer une instance de consignateur pour les utils kotlin statiques (comme moi :), vous pouvez utiliser cet assistant:
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())
Exemple d'utilisation:
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
}
Cette instruction fonctionne bien:
Thread.currentThread().getStackTrace()[1].getClassName();
Vous pouvez faire quelque chose de vraiment sympa en utilisant JNI comme ceci:
MonObjet.Java:
public class MyObject
{
static
{
System.loadLibrary( "classname" );
}
public static native String getClassName();
public static void main( String[] args )
{
System.out.println( getClassName() );
}
}
ensuite:
javac MyObject.Java
javah -jni MyObject
ensuite:
MonObjet.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 );
}
Puis compilez le C dans une bibliothèque partagée appelée libclassname.so
et exécutez Java!
*glousser
J'utilise cela pour lancer le Log4j Logger en haut de mes classes (ou annoter).
PRO: Throwable est déjà chargé et vous pouvez économiser des ressources en n'utilisant pas le SecurityManager "IO heavy".
CON: On peut se demander si cela fonctionnera pour toutes les machines virtuelles.
// 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());
Abuser du SecurityManager
System.getSecurityManager().getClassContext()[0].getName();
Ou, si non défini, utilisez une classe interne qui l'étend (exemple ci-dessous, copié honteusement de HowTo de Real ):
public static class CurrentClassGetter extends SecurityManager {
public String getClassName() {
return getClassContext()[1].getName();
}
}
Si vous voulez le nom complet du paquet avec, appelez:
String name = MyClass.class.getCanonicalName();
Si vous ne voulez que le dernier élément, appelez:
String name = MyClass.class.getSimpleName();
L'utilisation verbatim de la classe de l'appelant telle que MyClass.class.getName()
effectue le travail, mais est susceptible de copier/coller des erreurs si vous propagez ce code vers de nombreuses classes/sous-classes pour lesquelles vous avez besoin de ce nom de classe.
Et recette de Tom Hawtin n'est en fait pas mauvais, il suffit de le cuisiner correctement :)
Si vous avez une classe de base avec une méthode statique pouvant être appelée à partir de sous-classes, et que cette méthode statique ait besoin de connaître la classe de l'appelant, cela peut être obtenu comme suit:
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);
}
}
Une solution sécurisée pour le refactoring et le copier-coller qui évite la définition de classes ad-hoc ci-dessous.
Ecrivez une méthode statique qui récupère le nom de la classe en prenant soin d'inclure le nom de la classe dans le nom de la méthode:
private static String getMyClassName(){
return MyClass.class.getName();
}
puis rappelez-le dans votre méthode statique:
public static void myMethod(){
Tracer.debug(getMyClassName(), "message");
}
La sécurité du refactoring est obtenue en évitant l'utilisation de chaînes, la sécurité du copier-coller est garantie, car si vous coupez et collez la méthode de l'appelant, vous ne trouverez pas le getMyClassName () dans la classe cible "MyClass2", vous serez donc obligé de le redéfinir et de le mettre à jour.
Depuis la question Quelque chose comme `this.class` au lieu de` ClassName.class`?? est marqué comme un doublon pour celui-ci (ce qui est discutable car cette question concerne la classe plutôt que le nom de la classe), Je poste la réponse ici:
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);
}
}
Il est important de définir thisClass
comme privé car:
1) il ne doit pas être hérité: les classes dérivées doivent soit définir leur propre thisClass
, soit produire un message d'erreur
2) les références d'autres classes doivent être faites sous la forme ClassName.class
plutôt que ClassName.thisClass
.
Avec thisClass
défini, l'accès au nom de la classe devient:
thisClass.getName()
J'avais besoin du nom de classe dans les méthodes statiques de plusieurs classes. J'ai donc implémenté une classe JavaUtil avec la méthode suivante:
public static String getClassName() {
String className = Thread.currentThread().getStackTrace()[2].getClassName();
int lastIndex = className.lastIndexOf('.');
return className.substring(lastIndex + 1);
}
J'espère que ça va aider!
J'ai utilisé ces deux approches pour les deux scénarios static
et non static
:
Classe principale:
//For non static approach
public AndroidLogger(Object classObject) {
mClassName = classObject.getClass().getSimpleName();
}
//For static approach
public AndroidLogger(String className) {
mClassName = className;
}
Comment fournir le nom de la classe:
manière non statique:
private AndroidLogger mLogger = new AndroidLogger(this);
manière statique:
private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());