Ottenere il nome della classe da un metodo statico in Java


244

Come si può ottenere il nome della classe da un metodo statico in quella classe. Per esempio

public class MyClass {
    public static String getClassName() {
        String name = ????; // what goes here so the string "MyClass" is returned
        return name;
    }
}

Per metterlo nel contesto, in realtà voglio restituire il nome della classe come parte di un messaggio in un'eccezione.


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

Risposte:


231

Per supportare correttamente il refactoring (rinomina classe), è necessario utilizzare:

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

o (grazie a @James Van Huis ):

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

136
Se hai intenzione di scrivere un codice hard in conoscenza di MyClass in questo modo, allora potresti anche fare String name = "MyClass"; !
John Topley,

111
Tuttavia, il refactoring del nome della classe nell'IDE non funzionerà correttamente.
James Van Huis,

9
Vero. Sebbene MyClass.class assicurerà che questa riga non venga dimenticata con un refactoring "cambia nome classe"
toolkit

19
Vorrei che "questo" funzionasse in un contesto statico per indicare l'attuale Classe in Java, che quel "class.xxx" era permesso in entrambe le istanze o codice statico per indicare questa classe! Il problema è che MyClass è dettagliato e ridondante, nel contesto. Ma poi, per quanto mi piaccia Java, sembra incline alla verbosità.
Lawrence Dol,

51
Cosa succede se sto chiamando il metodo statico in una sottoclasse e desidero il nome della sottoclasse?
Edward Falk,

120

Fai quello che dice il toolkit. Non fare nulla del genere:

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

7
Se la classe ne estende un'altra, questa non restituisce la classe effettiva, ma solo la classe base.
Luis Soeiro,

1
@LuisSoeiro Credo che restituisca la classe in cui è definito il metodo. Non sono sicuro di come la classe base si inserisca nel contesto statico.
Tom Hawtin - tackline

8
Non capisco perché getClass () non possa essere statico. Questo "linguaggio" non sarebbe quindi necessario.
mmirwaldt

1
@mmirwaldt getClassrestituisce il tipo di runtime, quindi non può essere statico.
Tom Hawtin - affronta il

5
Questa è la vera risposta. Perché non devi scrivere tu stesso il nome della tua classe nel tuo codice.
Bob

99

In Java 7+ puoi farlo in metodi / campi statici:

MethodHandles.lookup().lookupClass()

Stavo per dire Reflection.getCallerClass(). Ma dà un avvertimento sull'essere nei pacchetti "sole". Quindi questa potrebbe essere una soluzione migliore.
Foumpie,

1
@Foumpie: Java 9 introdurrà un'API ufficiale che sostituirà questa Reflection.getCallerClass()cosa non ufficiale . È un po 'complicato per la sua banale operazione, vale a dire Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, ma ovviamente, è collegato al fatto che sarà molto più potente.
Holger,

6
Questa è facilmente la soluzione migliore. Evita la necessità di specificare il nome della classe attuale, non è oscuro, non è un hack e secondo il post di Artyom Krivolapov sotto è anche di gran lunga l'approccio più veloce.
skomisa,

@Rein C'è un modo per ottenere la classe di runtime se questa è stata chiamata nella classe di base?
Glide,

59

Quindi, abbiamo una situazione in cui dobbiamo ottenere staticamente un oggetto classe o un nome completo / semplice di classe senza un uso esplicito della MyClass.classsintassi.

Può essere davvero utile in alcuni casi, ad esempio l'istanza del logger per funzioni di livello superiore (in questo caso kotlin crea una classe Java statica non accessibile dal codice kotlin).

Abbiamo alcune varianti per ottenere queste informazioni:

  1. new Object(){}.getClass().getEnclosingClass();
    notato da Tom Hawtin - tackline

  2. getClassContext()[0].getName();dal SecurityManager
    noto di Christoffer

  3. new Throwable().getStackTrace()[0].getClassName();
    dal conte Ludwig

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

  5. e finalmente fantastico
    MethodHandles.lookup().lookupClass();
    da Rein


Ho preparato un benchmark per tutte le varianti e risultati sono:

# 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


conclusioni

  1. La migliore variante da usare , piuttosto pulita e mostruosamente veloce.
    Disponibile solo da Java 7 e Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Nel caso in cui sia necessaria questa funzionalità per Android o Java 6, è possibile utilizzare la seconda variante migliore. È anche piuttosto veloce, ma crea una classe anonima in ogni luogo di utilizzo :(
 new Object(){}.getClass().getEnclosingClass();
  1. Se ne hai bisogno in molti luoghi e non vuoi che il tuo bytecode si gonfi a causa di tonnellate di classi anonime, SecurityManagerè il tuo amico (terza opzione migliore).

    Ma non puoi semplicemente chiamare getClassContext(): è protetto in SecurityManagerclasse. Avrai bisogno di una classe di supporto come questa:

 // 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. Probabilmente non avrai mai bisogno di usare le ultime due varianti in base getStackTrace()all'eccezione from o alla Thread.currentThread(). Molto inefficiente e può restituire solo il nome della classe come a String, non l' Class<*>istanza.


PS

Se vuoi creare un'istanza del logger per utility kotlin statiche (come me :), puoi usare questo helper:

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

Esempio di utilizzo:

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

Questa istruzione funziona bene:

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

5
Stai attento, può essere molto lento. Tuttavia puoi copiarlo incollandolo.
Gábor Lipták,

1
Ciò ha l'ulteriore vantaggio di non dover creare un oggetto o un thread ogni volta che lo si utilizza.
Erel Segal-Halevi,

5
@ErelSegalHalevi Crea un sacco di StackTraceElement in background però :(
Navin

4
Se guardi il codice sorgente di Thread.getStackTrace()vedrai che non fa altro che return (new Exception()).getStackTrace();nel caso di essere chiamato sul currentThread(). Quindi la soluzione di @count Ludwig è il modo più diretto per ottenere lo stesso.
T-Bull,

34

Puoi fare qualcosa di veramente dolce usando JNI in questo modo:

MyObject.java:

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

    public static native String getClassName();

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

poi:

javac MyObject.java
javah -jni MyObject

poi:

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

Quindi compila il C in una libreria condivisa chiamata libclassname.soed esegui java!

* risatina


Perché questo non è integrato?
ggb667,

Intelligente e apprezzo l'umorismo. La mosca nell'unguento è che il nome predefinito della funzione C Java_MyObject_getClassNameha il nome incorporato. Il modo per aggirare questo è usare il JNI RegisterNatives. Ovviamente dovresti dargli da mangiare con il JNI FindClass(env, 'com/example/MyObject'), quindi non vincere neanche lì.
Rinnova il

2
@Renate, quindi tutta questa risposta è in realtà uno scherzo? Se è così, ti preghiamo di renderlo molto esplicito perché sai, dovremmo aiutare altre persone, quindi non spingiamo gli innocenti nelle trappole.
Stéphane Gourichon,

Beh, non era il mio scherzo e ho sottolineato che era uno scherzo. Il caso d'uso per questo scenario è di solito per identificare la classe nei registri. Eliminando tutta la complessità, di solito si riduce a fare una delle due cose: private static final String TAG = "MyClass"o private static final String TAG = MyClass.class.getSimpleName();Il secondo è più amichevole per la ridenominazione di classe globale usando un IDE.
Renate

20

Lo uso per avviare Log4j Logger nella parte superiore delle mie lezioni (o annotare).

PRO: Throwable è già caricato e potresti risparmiare risorse non usando il SecurityManager "IO pesante".

CON: Qualche domanda sul funzionamento di tutte le 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()); 

Crea la tua classe di eccezioni in modo che jvm non si preoccupi: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E

1
Se hai intenzione di suggerire qualcosa di terribile come questa soluzione, almeno collega i tuoi professionisti alla soluzione naturale dell'utilizzo di MyClass.class.getName (), invece di un'altra orribile soluzione come abusare di SecurityManager.
Søren Boisen,

Più contro: troppo prolisso; lento (questo in realtà è un punto minore perché viene eseguito solo una volta, quando la classe viene caricata).
Toolforger,

13

Abusa di SecurityManager

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

Oppure, se non impostato, utilizzare una classe interna che la estende (esempio riportato di seguito vergognosamente copiato dal HowTo di Real ):

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

9

Se si desidera l'intero nome del pacchetto con esso, chiamare:

String name = MyClass.class.getCanonicalName();

Se vuoi solo l'ultimo elemento, chiama:

String name = MyClass.class.getSimpleName();

5

L'uso verbale della classe del chiamante come MyClass.class.getName()effettivamente fa il lavoro, ma è incline a copiare / incollare errori se si propaga questo codice a numerose classi / sottoclassi in cui è necessario questo nome di classe.

E la ricetta di Tom Hawtin non è male, bisogna solo cucinarla nel modo giusto :)

Nel caso in cui si abbia una classe base con un metodo statico che può essere chiamato da sottoclassi e questo metodo statico deve conoscere la classe del chiamante effettiva, ciò può essere ottenuto come il seguente:

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

Una soluzione sicura per refactoring, taglia e incolla che evita la definizione delle classi ad hoc di seguito.

Scrivi un metodo statico che ripristini il nome della classe avendo cura di includere il nome della classe nel nome del metodo:

private static String getMyClassName(){
  return MyClass.class.getName();
}

quindi richiamalo nel tuo metodo statico:

public static void myMethod(){
  Tracer.debug(getMyClassName(), "message");
}

La sicurezza del refactoring viene data evitando l'uso di stringhe, la sicurezza taglia e incolla è garantita perché se si taglia e incolla il metodo del chiamante non si troverà getMyClassName () nella classe "MyClass2" di destinazione, quindi si sarà costretti a ridefinire e aggiornarlo.


3

Dalla domanda Qualcosa come `this.class` invece di` ClassName.class`? è contrassegnato come duplicato per questo (il che è discutibile perché quella domanda riguarda la classe piuttosto che il nome della classe), sto postando la risposta qui:

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

È importante definire thisClasscome privato perché:
1) non deve essere ereditato: le classi derivate devono definirsi proprie thisClasso produrre un messaggio di errore
2) i riferimenti da altre classi devono essere fatti come ClassName.classanziché ClassName.thisClass.

Con thisClassdefinito, l'accesso al nome della classe diventa:

thisClass.getName()

1

Avevo bisogno del nome della classe nei metodi statici di più classi, quindi ho implementato una classe JavaUtil con il seguente metodo:

public static String getClassName() {
    String className = Thread.currentThread().getStackTrace()[2].getClassName();
    int lastIndex = className.lastIndexOf('.');
    return className.substring(lastIndex + 1);
}

Spero che possa aiutare!


1
Non solo è male usarlo a causa del numero magico 2 (che potrebbe facilmente provocare una NullPointerException), ma fai molto affidamento sull'accuratezza della macchina virtuale. Dal javadoc del metodo: * Alcune macchine virtuali possono, in alcune circostanze, omettere uno o più frame di stack dalla traccia dello stack. In casi estremi, una macchina virtuale che non ha informazioni di traccia stack relative a questo thread può restituire un array di lunghezza zero da questo metodo. *
Fucile

0

Ho usato questi due approcci per entrambi statice per lo non staticscenario:

Classe principale:

//For non static approach
public AndroidLogger(Object classObject) {
    mClassName = classObject.getClass().getSimpleName();
}

//For static approach
public AndroidLogger(String className) {
    mClassName = className;
}

Come fornire il nome della classe:

modo non statico:

private AndroidLogger mLogger = new AndroidLogger(this);

Modo statico:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());

-1

Se si utilizza la riflessione, è possibile ottenere l'oggetto Metodo e quindi:

method.getDeclaringClass().getName()

Per ottenere il metodo stesso, puoi probabilmente usare:

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

3
E come saprai cos'è: "nome della classe"? :)
Alfasin il

1
Class.forName("class name")Ti ho già dato una lezione. Perché vuoi recuperarlo tramite il metodo?
Sergey Irisov,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.