Uzyskiwanie nazwy klasy z metody statycznej w Javie

244

Jak można uzyskać nazwę klasy z metody statycznej w tej klasie. Na przykład

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

Aby umieścić to w kontekście, naprawdę chcę zwrócić nazwę klasy jako część komunikatu w wyjątku.

Mile D.
źródło
try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
arminvanbuuren,

Odpowiedzi:

231

Aby poprawnie obsługiwać refaktoryzację (zmiana nazwy klasy), należy użyć:

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

lub (dzięki @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more
zestaw narzędzi
źródło
136
Jeśli zamierzasz na stałe kodować znajomość MyClass w ten sposób, możesz równie dobrze zrobić String name = "MyClass"; !
John Topley,
111
Ale wtedy refaktoryzacja nazwy klasy w twoim IDE nie będzie działać poprawnie.
James Van Huis
9
Prawdziwe. Chociaż MyClass.class sprawi, że ten wiersz nie zostanie zapomniany dzięki refaktoryzacji „zmień nazwę klasy”
zestaw narzędzi
19
Chciałbym, aby „to” działało w kontekście statycznym, oznaczając bieżącą klasę w Javie, aby ten „class.xxx” był dozwolony w instancji lub w kodzie statycznym, aby oznaczać tę klasę! Problem polega na tym, że MyClass jest pełna i zbędna w kontekście. Ale tak jak ja lubię Javę, wydaje się, że skłania się w kierunku gadatliwości.
Lawrence Dol
51
Co jeśli wywołam metodę statyczną w podklasie i chcę nazwę podklasy?
Edward Falk
120

Rób to, co mówi zestaw narzędzi. Nie rób czegoś takiego:

return new Object() { }.getClass().getEnclosingClass();
Tom Hawtin - tackline
źródło
7
Jeśli klasa rozszerza inną, to nie zwraca faktycznej klasy, tylko klasę podstawową.
Luis Soeiro,
1
@LuisSoeiro Wydaje mi się, że zwraca klasę, w której metoda jest zdefiniowana. Nie jestem pewien, jak klasa podstawowa wpływa na kontekst statyczny.
Tom Hawtin - tackline
8
Nie rozumiem, dlaczego getClass () nie może być statyczny. Ten „idiom” nie byłby wtedy potrzebny.
mmirwaldt
1
@mmirwaldt getClasszwraca typ środowiska wykonawczego, więc nie może być statyczny.
Tom Hawtin - tackline
5
To jest prawdziwa odpowiedź. Ponieważ nie musisz samodzielnie wpisywać nazwy swojej klasy w kodzie.
Bobs
99

W Javie 7+ możesz to zrobić w statycznej metodzie / polach:

MethodHandles.lookup().lookupClass()
Wodza
źródło
Miałem powiedzieć Reflection.getCallerClass(). Ale daje ostrzeżenie o byciu w paczkach „słonecznych”. To może być lepsze rozwiązanie.
Foumpie,
1
@Foumpie: Java 9 zamierza wprowadzić oficjalny interfejs API, który zastąpi tę nieoficjalną Reflection.getCallerClass()rzecz. Jest to trochę skomplikowane dla jego trywialnej operacji, tj. Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());, Ale oczywiście wiąże się to z faktem, że będzie znacznie potężniejszy.
Holger
6
To jest z pewnością najlepsze rozwiązanie. Unika potrzeby podawania rzeczywistej nazwy klasy, nie jest niejasna, nie jest hackowaniem i zgodnie z postem Artema Krivolapova poniżej jest to również zdecydowanie najszybsze podejście.
skomisa
@ Ree Czy istnieje sposób na uzyskanie klasy środowiska wykonawczego, jeśli została ona wywołana w klasie podstawowej?
Szybuj
59

Mamy więc sytuację, w której musimy statycznie uzyskać obiekt klasy lub pełną / prostą nazwę klasy bez wyraźnego użycia MyClass.classskładni.

W niektórych przypadkach może być bardzo przydatny, np. Wystąpienie programu rejestrującego funkcje wyższego poziomu (w tym przypadku kotlin tworzy statyczną klasę Java niedostępną z kodu kotlin).

Mamy kilka różnych wariantów uzyskiwania tych informacji:

  1. new Object(){}.getClass().getEnclosingClass();
    zauważył Tom Hawtin - tackline

  2. getClassContext()[0].getName();z SecurityManager
    odnotowanej przez Christoffera

  3. new Throwable().getStackTrace()[0].getClassName();
    według hrabiego Ludwiga

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

  5. i wreszcie niesamowite
    MethodHandles.lookup().lookupClass();
    od Reina


Przygotowałem punktem odniesienia dla wszystkich wariantów i wyników są:

# 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


Wnioski

  1. Najlepszy wariant w użyciu , raczej czysty i potwornie szybki.
    Dostępne tylko od wersji Java 7 i Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Jeśli potrzebujesz tej funkcji dla Androida lub Java 6, możesz użyć drugiego najlepszego wariantu. Jest też dość szybki, ale tworzy anonimową klasę w każdym miejscu użytkowania :(
 new Object(){}.getClass().getEnclosingClass();
  1. Jeśli potrzebujesz go w wielu miejscach i nie chcesz, aby twój kod bajtowy wzdychał z powodu mnóstwa anonimowych klas - SecurityManagerjest twoim przyjacielem (trzecia najlepsza opcja).

    Ale nie możesz po prostu zadzwonić getClassContext()- jest chroniony w SecurityManagerklasie. Będziesz potrzebować klasy pomocniczej takiej jak ta:

 // 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. Prawdopodobnie nigdy nie musisz używać dwóch ostatnich wariantów na podstawie getStackTrace()wyjątku lub Thread.currentThread(). Bardzo nieefektywny i może zwracać tylko nazwę klasy jako, a Stringnie Class<*>instancję.


PS

Jeśli chcesz utworzyć instancję rejestratora dla statycznych narzędzi kotlin (takich jak ja :), możesz użyć tego pomocnika:

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

Przykład użycia:

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
}
Artyom Krivolapov
źródło
38

Ta instrukcja działa dobrze:

Thread.currentThread().getStackTrace()[1].getClassName();
Keksi
źródło
5
Uważaj, może być bardzo powolny. Możesz jednak skopiować i wkleić.
Gábor Lipták
1
Ma to dodatkową zaletę, że nie trzeba tworzyć obiektu ani wątku za każdym razem, gdy go używasz.
Erel Segal-Halevi
5
@ErelSegalHalevi Tworzy w tle mnóstwo StackTraceElement s :(
Navin
4
Jeśli spojrzysz na kod źródłowy Thread.getStackTrace(), zobaczysz, że nie robi nic innego niż return (new Exception()).getStackTrace();w przypadku wywołania w currentThread(). Tak więc rozwiązanie @count ludwig jest bardziej bezpośrednim sposobem na osiągnięcie tego samego.
T-Bull
34

Możesz zrobić coś naprawdę słodkiego, używając JNI w następujący sposób:

MyObject.java:

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

    public static native String getClassName();

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

następnie:

javac MyObject.java
javah -jni MyObject

następnie:

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

Następnie skompiluj C do wspólnej biblioteki o nazwie libclassname.soi uruchom java!

*chichot

całe życie
źródło
Dlaczego to nie jest wbudowane?
ggb667
Sprytne i doceniam humor. W maści jest to, że domyślna nazwa funkcji C Java_MyObject_getClassNamema osadzoną nazwę. Rozwiązaniem jest korzystanie z JNI RegisterNatives. Oczywiście trzeba by to nakarmić JNI FindClass(env, 'com/example/MyObject'), więc nie ma też wygranej.
Renate
2
@Renate, więc cała ta odpowiedź jest w rzeczywistości żartem? Jeśli tak, proszę, powiedz to bardzo wyraźnie, ponieważ wiesz, że powinniśmy pomagać innym ludziom, więc nie wpychajmy niewinnych w pułapki.
Stéphane Gourichon
Cóż, to nie był mój żart i zauważyłem, że to był żart. Przypadek użycia w tym scenariuszu dotyczy zwykle identyfikacji klasy w dziennikach. Pozbycie się całej złożoności, zwykle sprowadza się do zrobienia albo: private static final String TAG = "MyClass"albo private static final String TAG = MyClass.class.getSimpleName();Drugi jest bardziej przyjazny dla globalnej zmiany nazw klas za pomocą IDE.
Zmień
20

Używam tego do inicjowania Log4j Logger na szczycie moich klas (lub adnotacji).

PRO: Throwable jest już załadowany i możesz zaoszczędzić zasoby, nie używając programu SecurityManager „IO heavy”.

CON: Pewne pytanie, czy to zadziała dla wszystkich 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()); 
hrabia Ludwig
źródło
Utwórz własną klasę wyjątków, aby jvm nie zawracał sobie głowy: jhocr.googlecode.com/svn/trunk/src/main/java/com/googlecode/…
4F2E4A2E
1
Jeśli masz zamiar zasugerować coś tak okropnego jak to rozwiązanie, przynajmniej powiąż swoje zalety z naturalnym rozwiązaniem korzystania z MyClass.class.getName (), zamiast z innym okropnym rozwiązaniem, takim jak nadużywanie SecurityManager.
Søren Boisen
Więcej wad: zbyt gadatliwy; powolny (jest to właściwie drobny punkt, ponieważ działa tylko raz, gdy klasa jest załadowana).
toolforger
13

Nadużywaj SecurityManager

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

Lub, jeśli nie ustawione, użyj wewnętrznej klasy, która ją rozszerza (przykład poniżej wstydliwie skopiowany z How's Real ):

public static class CurrentClassGetter extends SecurityManager {
    public String getClassName() {
        return getClassContext()[1].getName(); 
    }
}
Christoffer
źródło
9

Jeśli chcesz z nim całą nazwę pakietu, zadzwoń:

String name = MyClass.class.getCanonicalName();

Jeśli chcesz tylko ostatni element, wywołaj:

String name = MyClass.class.getSimpleName();
James Van Huis
źródło
5

Dosłownie użycie klasy obiektu wywołującego tak jak MyClass.class.getName()faktycznie wykonuje to zadanie, ale jest podatne na błędy kopiowania / wklejania, jeśli propagujesz ten kod do wielu klas / podklas, w których potrzebujesz tej nazwy klasy.

A przepis Toma Hawtina nie jest wcale taki zły, wystarczy go odpowiednio ugotować :)

Jeśli masz klasę bazową z metodą statyczną, która może być wywoływana z podklas, a ta metoda statyczna musi znać rzeczywistą klasę dzwoniącego, można to osiągnąć w następujący sposób:

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);
  }
}
Siergiej Uszakow
źródło
4

Bezpieczne dla refaktoryzacji, bezpieczne dla cięcia i wklejania rozwiązanie, które pozwala uniknąć definicji klas ad-hoc poniżej.

Napisz metodę statyczną, która odzyskuje nazwę klasy, starając się zawrzeć nazwę klasy w nazwie metody:

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

następnie przywołaj to w metodzie statycznej:

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

Bezpieczeństwo refaktoryzacji jest zapewnione przez unikanie użycia ciągów, zapewnione jest bezpieczeństwo wycinania i wklejania, ponieważ jeśli wytniesz i wkleisz metodę wywołującą, nie znajdziesz funkcji getMyClassName () w docelowej klasie „MyClass2”, więc będziesz zmuszony ją przedefiniować i zaktualizować.

avalori
źródło
3

Ponieważ pytanie Coś w rodzaju „this.class` zamiast„ ClassName.class`? jest oznaczony jako duplikat tego (co jest dyskusyjne, ponieważ to pytanie dotyczy raczej klasy niż nazwy klasy), zamieszczam odpowiedź tutaj:

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

Ważne jest, aby zdefiniować thisClassjako prywatny, ponieważ:
1) nie można go dziedziczyć: klasy pochodne muszą albo zdefiniować własne, thisClassalbo wygenerować komunikat o błędzie
2) należy odwoływać się do innych klas ClassName.classzamiast ClassName.thisClass.

Po thisClasszdefiniowaniu dostęp do nazwy klasy staje się:

thisClass.getName()
18446744073709551615
źródło
1

Potrzebowałem nazwy klasy w metodach statycznych wielu klas, dlatego zaimplementowałem klasę JavaUtil za pomocą następującej metody:

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

Mam nadzieję, że to pomoże!

Phaderer
źródło
1
Nie tylko jest to złe ze względu na magiczną liczbę 2 (która może z łatwością spowodować wyjątek NullPointerException), ale w dużej mierze polegasz na dokładności maszyny wirtualnej. Z metody javadoc metody: * Niektóre maszyny wirtualne mogą, w niektórych okolicznościach, pominąć jedną lub więcej ramek stosu ze śledzenia stosu. W skrajnym przypadku maszyna wirtualna, która nie ma informacji o stosie dotyczących tego wątku, może zwrócić tablicę o zerowej długości z tej metody. *
Strzelba
0

Użyłem te dwa podejścia do obu statici non staticscenariusz:

Główna klasa:

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

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

Jak podać nazwę klasy:

sposób niestatyczny:

private AndroidLogger mLogger = new AndroidLogger(this);

Sposób statyczny:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
0xAliHn
źródło
-1

Jeśli używasz odbicia, możesz uzyskać obiekt Method, a następnie:

method.getDeclaringClass().getName()

Aby uzyskać samą metodę, prawdopodobnie możesz użyć:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)
Franzé Jr.
źródło
3
A skąd będziesz wiedzieć, co to jest: „nazwa klasy”? :)
alfasin
1
Dając ci Class.forName("class name")już klasę. Dlaczego chcesz go odzyskać metodą?
Sergey Irisov