web-dev-qa-db-fra.com

Obtenir le nom de la classe à partir d'une méthode statique dans Java

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.

226
Miles D

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
219
toolkit

Faites ce que dit la boîte à outils. Ne faites rien comme ça:

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

Dans Java 7+, vous pouvez le faire avec des méthodes/champs statiques:

MethodHandles.lookup().lookupClass()
81
Rein

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:

  1. new Object(){}.getClass().getEnclosingClass();
    noté par Tom Hawtin - tackline

  2. getClassContext()[0].getName(); de la SecurityManager
    noté par Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    by count ludwig

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

  5. et enfin génial
    MethodHandles.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


Conclusions

  1. Meilleure variante à utiliser , plutôt propre et monstrueusement rapide.
    Disponible uniquement depuis Java 7 et Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Si vous avez besoin de cette fonctionnalité pour Android ou Java 6, vous pouvez utiliser la deuxième meilleure variante. C'est assez rapide aussi, mais crée une classe anonyme dans chaque lieu d'utilisation :(
 new Object(){}.getClass().getEnclosingClass();
  1. 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())
 }
  1. Vous n'avez probablement jamais besoin d'utiliser les deux dernières variantes basées sur 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<*>.


P.S.

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
}
41

Cette instruction fonctionne bien:

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

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

34
lifelongcoug

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()); 
20
count ludwig

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(); 
    }
}
13
Christoffer

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();
9
James Van Huis

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);
  }
}
5
Sergey Ushakov

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.

4
avalori

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

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!

1
phaderer

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());
0
0xAliHn