Obtention du nom de classe à partir d'une méthode statique en Java


244

Comment 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 veux en fait retourner le nom de classe dans le cadre d'un message dans une exception.


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

Réponses:


231

Afin de prendre en charge correctement le refactoring (renommer la classe), vous devez utiliser soit:

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

ou (merci à @James Van Huis ):

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

136
Si vous allez coder en dur dans la connaissance de MyClass comme ça, alors vous pourriez aussi bien faire String name = "MyClass"; !
John Topley

111
Mais alors, la refactorisation du nom de classe dans votre IDE ne fonctionnera pas correctement.
James Van Huis

9
Vrai. Bien que MyClass.class fera en sorte que cette ligne ne soit pas oubliée avec un refactoring 'change class name'
toolkit

19
Je souhaite que "ceci" fonctionne dans un contexte statique pour signifier la classe actuelle en Java, que "class.xxx" soit autorisé dans une instance ou dans du code statique pour signifier cette classe! Le problème avec cela est que MyClass est verbeux et redondant, dans le contexte. Mais autant que j'aime Java, il semble pencher vers la verbosité.
Lawrence Dol

51
Que faire si j'appelle la méthode statique dans une sous-classe et que je veux le nom de la sous-classe?
Edward Falk

120

Faites ce que dit la boîte à outils. Ne faites rien de tel:

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

7
Si la classe en étend une autre, cela ne renvoie pas la classe réelle, seulement la classe de base.
Luis Soeiro

1
@LuisSoeiro Je crois que cela renvoie la classe dans laquelle la méthode est définie. Je ne sais pas comment la classe de base prend en compte le contexte statique.
Tom Hawtin - tackline

8
Je ne comprends pas pourquoi getClass () ne peut pas être statique. Cet "idiome" ne serait alors plus nécessaire.
mmirwaldt

1
@mmirwaldt getClassrenvoie le type d'exécution, il ne peut donc pas être statique.
Tom Hawtin - tackline

5
Telle est la vraie réponse. Parce que vous n'avez pas besoin d'écrire vous-même le nom de votre classe dans votre code.
Bobs

99

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

MethodHandles.lookup().lookupClass()

J'allais dire Reflection.getCallerClass(). Mais il donne un avertissement sur le fait d'être dans les packages «soleil». Cela pourrait donc être une meilleure solution.
Foumpie

1
@Foumpie: Java 9 va introduire une API officielle qui remplacera cette Reflection.getCallerClass()chose non officielle . C'est un peu compliqué pour son opération triviale, c'est-à-dire Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, mais bien sûr, cela est lié au fait qu'il sera beaucoup plus puissant.
Holger

6
C'est facilement la meilleure solution. Cela évite d'avoir à spécifier le nom réel de la classe, ce n'est pas obscur, ce n'est pas un hack et selon le post d'Artyom Krivolapov ci-dessous, c'est aussi de loin l'approche la plus rapide.
skomisa

@Rein Existe-t-il un moyen d'obtenir la classe d'exécution si elle a été appelée dans la classe de base?
Glide

59

Donc, nous avons une situation où nous devons obtenir statiquement un objet de classe ou un nom complet / simple de classe sans utilisation explicite de la MyClass.classsyntaxe.

Il peut être très pratique dans certains cas, par exemple l'instance de l'enregistreur pour le fonctions de niveau supérieur (dans ce cas, kotlin crée une classe Java statique non accessible à partir du code kotlin).

Nous avons quelques variantes différentes pour obtenir ces informations:

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

  2. getClassContext()[0].getName();de la SecurityManager
    note de Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    par le comte ludwig

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

  5. et enfin génial
    MethodHandles.lookup().lookupClass();
    de Rein


J'ai préparé un les références pour toutes les variantes et tous les résultats sont:

# 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 en raison de tonnes de classes anonymes - SecurityManagerc'est votre ami (troisième meilleure option).

    Mais vous ne pouvez pas simplement appeler getClassContext()- c'est protégé dans la SecurityManagerclasse. 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 l' getStackTrace()exception from ou la Thread.currentThread(). Très inefficace et ne peut renvoyer que le nom de classe en tant que a String, pas l' Class<*>instance.


PS

Si vous souhaitez créer une instance d'enregistreur pour les utilitaires de 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
}

38

Cette instruction fonctionne bien:

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

5
Attention, cela peut être très lent. Cependant, vous pouvez copier-coller.
Gábor Lipták

1
Cela a l'avantage supplémentaire de ne pas avoir à créer un objet ou un thread chaque fois que vous l'utilisez.
Erel Segal-Halevi

5
@ErelSegalHalevi Cela crée cependant beaucoup de StackTraceElement en arrière-plan :(
Navin

4
Si vous regardez le code source de Thread.getStackTrace()vous verrez qu'il ne fait rien d'autre que return (new Exception()).getStackTrace();dans le cas d'être appelé sur le currentThread(). La solution de @count ludwig est donc le moyen le plus direct d'y parvenir.
T-Bull

34

Vous pouvez faire quelque chose de vraiment doux en utilisant JNI comme ceci:

MyObject.java:

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

    public static native String getClassName();

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

puis:

javac MyObject.java
javah -jni MyObject

puis:

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

Ensuite, compilez le C dans une bibliothèque partagée appelée libclassname.soet exécutez le java!

*glousser


Pourquoi cela n'est-il pas intégré?
ggb667

Intelligent, et j'apprécie l'humour. La mouche dans la pommade est que le nom par défaut de la fonction C Java_MyObject_getClassNamea le nom intégré. La solution consiste à utiliser le JNI RegisterNatives. Bien sûr, il faudrait nourrir cela avec le JNI FindClass(env, 'com/example/MyObject'), donc pas de victoire là-bas non plus.
Renate

2
@Renate, donc toute cette réponse est en fait une blague? Si c'est le cas, veuillez le rendre très explicite parce que vous savez, nous sommes censés aider d'autres personnes, alors ne poussons pas des innocents dans des pièges.
Stéphane Gourichon

Eh bien, ce n'était pas ma blague et j'ai souligné que c'était une blague. Le cas d'utilisation de ce scénario est généralement pour identifier la classe dans les journaux. Dépouillant toute la complexité, cela se résume généralement à faire l'une private static final String TAG = "MyClass"ou l' autre: ou private static final String TAG = MyClass.class.getSimpleName();La seconde est plus conviviale pour le changement de nom de classe globale à l'aide d'un IDE.
Renate le

20

Je l'utilise pour initier 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: Certains se demandent si cela fonctionnera pour toutes les 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()); 

Créez votre propre classe d'exceptions pour que jvm ne dérange pas: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E

1
Si vous allez suggérer quelque chose d'aussi horrible que cette solution, veuillez au moins relier vos pros à la solution naturelle d'utiliser MyClass.class.getName (), au lieu d'une autre solution horrible comme abuser du SecurityManager.
Søren Boisen

Plus contre: trop verbeux; lent (c'est en fait un point mineur car il ne s'exécute qu'une seule fois, lorsque la classe est chargée).
toolforger

13

Abus du SecurityManager

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

Ou, s'il n'est pas défini, utilisez une classe interne qui l'étend (exemple ci-dessous copié honteusement à partir de HowTo de Real ):

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

9

Si vous voulez le nom complet du package avec lui, appelez:

String name = MyClass.class.getCanonicalName();

Si vous ne voulez que le dernier élément, appelez:

String name = MyClass.class.getSimpleName();

5

L'utilisation verbatim de la classe de l'appelant MyClass.class.getName()fait le travail, mais est sujette à des erreurs de copier / coller si vous propagez ce code à de nombreuses classes / sous-classes où vous avez besoin de ce nom de classe.

Et la recette de Tom Hawtin n'est en fait pas mauvaise, il suffit de la faire cuire correctement :)

Dans le cas où vous avez une classe de base avec une méthode statique qui peut être appelée à partir de sous-classes, et cette méthode statique doit connaître la classe de l'appelant réel, cela peut être réalisé 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);
  }
}

4

Une solution sûre pour le refactoring, coupe-coller qui évite la définition des classes ad-hoc ci-dessous.

Écrivez 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 refactorisation de la sécurité est donnée en évitant l'utilisation de chaînes, la sécurité de copier-coller est accordée 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.


3

Depuis la question Quelque chose comme «this.class» au lieu de «ClassName.class»? est marqué comme un doublon pour celui-ci (ce qui est défendable 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 thisClasscomme privé car:
1) il ne doit pas être hérité: les classes dérivées doivent soit définir les leurs, thisClasssoit produire un message d'erreur
2) les références à partir d'autres classes doivent être faites comme ClassName.classplutôt que ClassName.thisClass.

Avec thisClassdéfini, l'accès au nom de classe devient:

thisClass.getName()

1

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 cela vous aidera!


1
Non seulement il est mauvais de l'utiliser à cause du nombre magique 2 (qui pourrait facilement entraîner une NullPointerException), mais vous comptez beaucoup sur la précision de la machine virtuelle. À partir du javadoc de la méthode: * Certaines machines virtuelles peuvent, dans certaines circonstances, omettre une ou plusieurs trames de pile de la trace de pile. Dans le cas extrême, une machine virtuelle qui n'a aucune information de trace de pile concernant ce thread est autorisée à renvoyer un tableau de longueur nulle à partir de cette méthode. *
Shotgun

0

J'ai utilisé ces deux approches pour les deux staticet le non staticscénario:

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

-1

Si vous utilisez la réflexion, vous pouvez obtenir l'objet Method puis:

method.getDeclaringClass().getName()

Pour obtenir la méthode elle-même, vous pouvez probablement utiliser:

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

3
Et comment saurez-vous ce qu'est: "nom de classe"? :)
alfasin

1
Vous ayant Class.forName("class name")déjà donné un cours. Pourquoi voulez-vous le récupérer via la méthode?
Sergey Irisov
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.