Abrufen des Klassennamens von einer statischen Methode in Java

244

Wie kann man den Namen der Klasse von einer statischen Methode in dieser Klasse erhalten? Beispielsweise

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

Um es in einen Kontext zu setzen, möchte ich den Klassennamen tatsächlich als Teil einer Nachricht in einer Ausnahme zurückgeben.

Meilen D.
quelle
try{ throw new RuntimeEsception();} catch(RuntimeEcxeption e){return e.getstackTrace()[1].getClassName();}
Arminvanbuuren

Antworten:

231

Um das Refactoring korrekt zu unterstützen (Klasse umbenennen), sollten Sie Folgendes verwenden:

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

oder (danke an @James Van Huis ):

 MyClass.class.getSimpleName(); // class name and no more
Toolkit
quelle
136
Wenn Sie in Kenntnis von MyClass so hart codieren möchten, können Sie auch einfach String name = "MyClass" ausführen. !
John Topley
111
Das Refactoring des Klassennamens in Ihrer IDE funktioniert jedoch nicht ordnungsgemäß.
James Van Huis
9
Wahr. Obwohl MyClass.class sicherstellt, dass diese Zeile nicht mit einem Refactoring "Änderungsklassenname" vergessen wird
Toolkit
19
Ich wünschte, "dies" würde in einem statischen Kontext funktionieren, um die aktuelle Klasse in Java zu bedeuten, dass "class.xxx" entweder in einer Instanz oder in statischem Code diese Klasse bedeuten dürfe! Das Problem dabei ist, dass MyClass im Kontext ausführlich und redundant ist. Aber so sehr ich Java mag, scheint es eher zur Ausführlichkeit zu tendieren.
Lawrence Dol
51
Was ist, wenn ich die statische Methode in einer Unterklasse aufrufe und den Namen der Unterklasse möchte?
Edward Falk
120

Mach was das Toolkit sagt. Mach so etwas nicht:

return new Object() { }.getClass().getEnclosingClass();
Tom Hawtin - Tackline
quelle
7
Wenn die Klasse eine andere erweitert, gibt dies nicht die tatsächliche Klasse zurück, sondern nur die Basisklasse.
Luis Soeiro
1
@LuisSoeiro Ich glaube, es gibt die Klasse zurück, in der die Methode definiert ist. Ich bin nicht sicher, wie die Basisklasse in den statischen Kontext einfließt.
Tom Hawtin - Tackline
8
Ich verstehe nicht, warum getClass () nicht statisch sein kann. Diese "Redewendung" würde dann nicht benötigt.
mmirwaldt
1
@mmirwaldt getClassgibt den Laufzeittyp zurück, kann also nicht statisch sein.
Tom Hawtin - Tackline
5
Das ist die wahre Antwort. Weil Sie den Namen Ihrer Klasse nicht selbst in Ihren Code schreiben müssen.
Bobs
99

In Java 7+ können Sie dies in statischen Methoden / Feldern tun:

MethodHandles.lookup().lookupClass()
Zügel
quelle
Ich wollte sagen Reflection.getCallerClass(). Aber es gibt eine Warnung davor, in den "Sonnen" -Paketen zu sein. Dies könnte also eine bessere Lösung sein.
Foumpie
1
@Foumpie: Java 9 wird eine offizielle API einführen, die diese inoffizielle Reflection.getCallerClass()Sache ersetzen wird . Es ist ein bisschen kompliziert für seine triviale Operation, dh Optional<Class<?>> myself = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(s -> s.map(StackWalker.StackFrame::getDeclaringClass) .findFirst());natürlich hängt das damit zusammen, dass es viel mächtiger sein wird.
Holger
6
Dies ist leicht die beste Lösung. Es vermeidet die Notwendigkeit, den tatsächlichen Klassennamen anzugeben, es ist nicht dunkel, es ist kein Hack und laut Artyom Krivolapovs Post unten ist es auch bei weitem der schnellste Ansatz.
Skomisa
@Rein Gibt es eine Möglichkeit, die Laufzeitklasse abzurufen, wenn diese in der Basisklasse aufgerufen wurde?
Gleiten Sie am
59

Wir haben also eine Situation, in der wir ein Klassenobjekt oder einen vollständigen / einfachen Klassennamen ohne explizite Verwendung von statisch abrufen müssen MyClass.class Syntax abrufen müssen.

In einigen Fällen kann es sehr praktisch sein, z. B. eine Logger-Instanz für die Funktionen der oberen Ebene (in diesem Fall erstellt kotlin eine statische Java-Klasse, auf die über den kotlin-Code nicht zugegriffen werden kann).

Wir haben verschiedene Varianten, um diese Informationen zu erhalten:

  1. new Object(){}.getClass().getEnclosingClass();
    notiert von Tom Hawtin - Tackline

  2. getClassContext()[0].getName();aus dem SecurityManager
    von Christoffer notierten

  3. new Throwable().getStackTrace()[0].getClassName();
    von Graf Ludwig

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

  5. und schließlich super
    MethodHandles.lookup().lookupClass();
    von Rein


Ich habe eine vorbereitet Benchmark für alle Varianten und Ergebnisse sind:

# 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


Schlussfolgerungen

  1. Beste Variante , ziemlich sauber und ungeheuer schnell.
    Verfügbar nur seit Java 7 und Android API 26!
 MethodHandles.lookup().lookupClass();
  1. Wenn Sie diese Funktionalität für Android oder Java 6 benötigen, können Sie die zweitbeste Variante verwenden. Es ist auch ziemlich schnell, erstellt aber an jedem Verwendungsort eine anonyme Klasse :(
 new Object(){}.getClass().getEnclosingClass();
  1. Wenn Sie es an vielen Orten benötigen und nicht möchten, dass Ihr Bytecode aufgrund unzähliger anonymer Klassen aufgebläht wird, SecurityManagerist dies Ihr Freund (drittbeste Option).

    Aber Sie können nicht einfach anrufen getClassContext()- es ist in der SecurityManagerKlasse geschützt . Sie benötigen eine Helferklasse wie diese:

 // 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. Sie müssen wahrscheinlich nie die letzten beiden Varianten verwenden, die auf der getStackTrace()from-Ausnahme oder der from-Ausnahme basieren Thread.currentThread(). Sehr ineffizient und kann nur den Klassennamen als String, nicht die Class<*>Instanz zurückgeben.


PS

Wenn Sie eine Logger-Instanz für statische Kotlin-Utils erstellen möchten (wie ich :), können Sie diesen Helfer verwenden:

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

Anwendungsbeispiel:

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
quelle
38

Diese Anweisung funktioniert gut:

Thread.currentThread().getStackTrace()[1].getClassName();
Keksi
quelle
5
Achten Sie darauf, dass es sehr langsam sein kann. Sie können es jedoch kopieren und einfügen.
Gábor Lipták
1
Dies hat den zusätzlichen Vorteil, dass Sie nicht jedes Mal ein Objekt oder einen Thread erstellen müssen, wenn Sie es verwenden.
Erel Segal-Halevi
5
@ErelSegalHalevi Es erstellt jedoch eine ganze Reihe von StackTraceElement s im Hintergrund :(
Navin
4
Wenn Sie sich den Quellcode von Thread.getStackTrace()ansehen, werden Sie feststellen, dass er nichts anderes tut, als return (new Exception()).getStackTrace();wenn er auf dem aufgerufen wird currentThread(). Die Lösung von @count ludwig ist also der direktere Weg, um dasselbe zu erreichen.
T-Bull
34

Sie könnten etwas wirklich Süßes tun, indem Sie JNI wie folgt verwenden:

MyObject.java:

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

    public static native String getClassName();

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

dann:

javac MyObject.java
javah -jni MyObject

dann:

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

Kompilieren Sie dann das C in eine gemeinsam genutzte Bibliothek mit dem Namen libclassname.so und führen Sie Java aus!

*Kichern

lebenslanger Coug
quelle
Warum ist das nicht eingebaut?
ggb667
Clever, und ich schätze den Humor. Die Fliege in der Salbe ist, dass der Standardname der C-Funktion Java_MyObject_getClassNameden Namen eingebettet hat. Der Weg dahin ist die Verwendung des JNI RegisterNatives. Natürlich müsste man das mit dem JNI füttern FindClass(env, 'com/example/MyObject'), also auch dort kein Gewinn.
Renate
2
@ Renate, also ist diese ganze Antwort eigentlich ein Witz? Wenn ja, machen Sie es bitte sehr deutlich, denn Sie wissen, wir sollen anderen Menschen helfen, also lasst uns keine Unschuldigen in Fallen schieben.
Stéphane Gourichon
Nun, es war nicht mein Witz und ich wies darauf hin, dass es ein Witz war. Der Anwendungsfall für dieses Szenario besteht normalerweise darin, die Klasse in Protokollen zu identifizieren. Wenn die Komplexität wegfällt, läuft es normalerweise darauf hinaus, entweder: private static final String TAG = "MyClass"oder private static final String TAG = MyClass.class.getSimpleName();Die zweite ist für das Umbenennen globaler Klassen mithilfe einer IDE benutzerfreundlicher.
Renate
20

Ich verwende dies, um den Log4j-Logger oben in meinen Klassen zu starten (oder zu kommentieren).

PRO: Throwable ist bereits geladen und Sie können Ressourcen sparen, wenn Sie den SecurityManager "IO heavy" nicht verwenden.

CON: Einige Fragen, ob dies für alle JVMs funktioniert.

// 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()); 
Graf Ludwig
quelle
Erstellen Sie Ihre eigene Ausnahmeklasse, damit sich das JVM nicht darum kümmert
4F2E4A2E
1
Wenn Sie etwas so Schreckliches wie diese Lösung vorschlagen, beziehen Sie Ihre Profis zumindest auf die natürliche Lösung der Verwendung von MyClass.class.getName (), anstatt auf eine andere schreckliche Lösung wie den Missbrauch des SecurityManager.
Søren Boisen
Weitere Nachteile: zu ausführlich; langsam (dies ist eigentlich ein kleiner Punkt, da es nur einmal ausgeführt wird, wenn die Klasse geladen wird).
Werkzeugschmiede
13

Missbrauche den SecurityManager

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

Oder, falls nicht festgelegt, verwenden Sie eine innere Klasse, die sie erweitert (Beispiel unten, schändlich kopiert von Real's HowTo ):

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

Wenn Sie den gesamten Paketnamen damit haben möchten, rufen Sie an:

String name = MyClass.class.getCanonicalName();

Wenn Sie nur das letzte Element möchten, rufen Sie auf:

String name = MyClass.class.getSimpleName();
James Van Huis
quelle
5

Die wörtliche Verwendung der Klasse des Aufrufers wie MyClass.class.getName()tatsächlich erledigt den Job, ist jedoch anfällig für das Kopieren / Einfügen von Fehlern, wenn Sie diesen Code an zahlreiche Klassen / Unterklassen weitergeben, in denen Sie diesen Klassennamen benötigen.

Und Tom Hawtins Rezept ist in der Tat nicht schlecht, man muss es nur richtig kochen :)

Wenn Sie eine Basisklasse mit einer statischen Methode haben, die aus Unterklassen aufgerufen werden kann, und diese statische Methode die Klasse des tatsächlichen Aufrufers kennen muss, kann dies wie folgt erreicht werden:

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

Eine Refactoring-sichere, Cut & Paste-sichere Lösung, die die Definition der folgenden Ad-hoc-Klassen vermeidet.

Schreiben Sie eine statische Methode, die den Klassennamen wiederherstellt, wobei Sie darauf achten, den Klassennamen in den Methodennamen aufzunehmen:

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

Rufen Sie es dann in Ihrer statischen Methode auf:

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

Die Refactoring-Sicherheit wird durch die Vermeidung der Verwendung von Zeichenfolgen gewährleistet. Die Sicherheit zum Ausschneiden und Einfügen wird gewährleistet, da beim Ausschneiden und Einfügen der Aufrufermethode die getMyClassName () in der Zielklasse "MyClass2" nicht gefunden wird und Sie daher gezwungen sind, sie neu zu definieren und zu aktualisieren.

Avalori
quelle
3

Seit der Frage Etwas wie "this.class" anstelle von "ClassName.class"? ist als Duplikat für dieses markiert (was fraglich ist, da es sich bei dieser Frage eher um die Klasse als um den Klassennamen handelt). Ich poste die Antwort hier:

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

Es ist wichtig, thisClassals privat zu definieren, weil:
1) es nicht vererbt werden darf: abgeleitete Klassen müssen entweder ihre eigenen definieren thisClassoder eine Fehlermeldung erzeugen.
2) Verweise von anderen Klassen sollten wie ClassName.classfolgt erfolgen ClassName.thisClass.

Mit thisClassdefined wird der Zugriff auf den Klassennamen wie folgt:

thisClass.getName()
18446744073709551615
quelle
1

Ich brauchte den Klassennamen in den statischen Methoden mehrerer Klassen, also implementierte ich eine JavaUtil-Klasse mit der folgenden Methode:

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

Hoffe es wird helfen!

Phaderer
quelle
1
Dies ist nicht nur wegen der magischen Nummer 2 (die leicht zu einer NullPointerException führen kann) schlecht, sondern Sie verlassen sich auch stark auf die Genauigkeit der virtuellen Maschine. Aus dem Javadoc der Methode: * Einige virtuelle Maschinen lassen unter Umständen einen oder mehrere Stapelrahmen aus der Stapelverfolgung aus. Im Extremfall darf eine virtuelle Maschine, die keine Stapelverfolgungsinformationen zu diesem Thread enthält, ein Array mit der Länge Null von dieser Methode zurückgeben. *
Schrotflinte
0

Ich habe diese beiden Ansätze für beide staticund non staticSzenarien verwendet:

Hauptklasse:

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

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

So geben Sie den Klassennamen an:

nicht statischer Weg:

private AndroidLogger mLogger = new AndroidLogger(this);

Statischer Weg:

private static AndroidLogger mLogger = new AndroidLogger(Myclass.class.getSimpleName());
0xAliHn
quelle
-1

Wenn Sie Reflection verwenden, können Sie das Method-Objekt abrufen und dann:

method.getDeclaringClass().getName()

Um die Methode selbst zu erhalten, können Sie wahrscheinlich Folgendes verwenden:

Class<?> c = Class.forName("class name");
Method  method = c.getDeclaredMethod ("method name", parameterTypes)
Franzé Jr.
quelle
3
Und woher wissen Sie, was "Klassenname" ist? :)
Alfasin
1
Ich habe Class.forName("class name")dir schon eine Klasse gegeben. Warum möchten Sie es über die Methode abrufen?
Sergey Irisov