Wie rufe ich getClass () von einer statischen Methode in Java auf?

350

Ich habe eine Klasse, die einige statische Methoden haben muss. Innerhalb dieser statischen Methoden muss ich die Methode getClass () aufrufen, um den folgenden Aufruf auszuführen:

public static void startMusic() {
  URL songPath = getClass().getClassLoader().getResource("background.midi");
}

Eclipse sagt mir jedoch:

Cannot make a static reference to the non-static method getClass() 
from the type Object

Was ist der geeignete Weg, um diesen Fehler bei der Kompilierung zu beheben?

Rama
quelle
Die Verwendung, getResource()bevor eine Instanz einer benutzerdefinierten Klasse (z. B. Nicht-J2SE) vorhanden ist, schlägt manchmal fehl. Das Problem ist, dass die JRE zu diesem Zeitpunkt den Bootstrap-Klassenlader verwendet, der keine Anwendungsressourcen im Klassenpfad (des Bootstrap-Laders) hat.
Andrew Thompson

Antworten:

616

Die Antwort

Verwenden Sie einfach TheClassName.classanstelle von getClass().

Logger deklarieren

Da dies für einen bestimmten Anwendungsfall so viel Aufmerksamkeit erhält - um eine einfache Möglichkeit zum Einfügen von Protokolldeklarationen zu bieten -, dachte ich, ich würde meine Gedanken dazu hinzufügen. Protokoll-Frameworks erwarten häufig, dass das Protokoll auf einen bestimmten Kontext beschränkt ist, z. B. einen vollständig qualifizierten Klassennamen. Sie können also nicht ohne Änderungen kopiert werden. Vorschläge für einfügesichere Protokolldeklarationen sind in anderen Antworten enthalten, haben jedoch Nachteile wie das Aufblasen von Bytecode oder das Hinzufügen von Introspection zur Laufzeit. Ich empfehle diese nicht. Das Kopieren und Einfügen ist ein Problem des Editors , daher ist eine Editorlösung am besten geeignet.

In IntelliJ empfehle ich das Hinzufügen einer Live-Vorlage:

  • Verwenden Sie "log" als Abkürzung
  • Verwenden Sie private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger($CLASS$.class);als Vorlage Text.
  • Klicken Sie auf Variablen bearbeiten und fügen Sie mithilfe des Ausdrucks KLASSE hinzu className()
  • Aktivieren Sie die Kontrollkästchen, um die FQ-Namen neu zu formatieren und zu kürzen.
  • Ändern Sie den Kontext in Java: Deklaration.

Wenn Sie jetzt eingeben, wird log<tab>es automatisch auf erweitert

private static final Logger logger = LoggerFactory.getLogger(ClassName.class);

Und formatieren und optimieren Sie die Importe automatisch für Sie neu.

Mark Peters
quelle
6
Dies führt zu einem Kopieren / Einfügen und traurigen, traurigen Ergebnissen, wenn der falsche Code in einer anderen Klasse ausgeführt wird.
William Entriken
1
@WilliamEntriken: Die Verwendung anonymer innerer Klassen ist keine gute Lösung dafür. Sie bläht nur Ihren Bytecode mit zusätzlichen Klassendateien auf, um ein paar Sekunden Entwickleraufwand zu sparen. Ich bin mir nicht sicher, was die Leute tun, wo sie überall kopieren / einfügen müssen, aber es sollte mit einer Editorlösung behandelt werden, nicht mit einem Sprachhack. Wenn Sie beispielsweise IntelliJ verwenden, verwenden Sie eine Live-Vorlage, in die der Klassenname eingefügt werden kann.
Mark Peters
1
Ich frage mich wirklich, warum Sie 4 Abstimmungen erhalten haben
Al-Mothafar
"Ich bin nicht sicher, was die Leute tun, wo sie überall kopieren / einfügen müssen" - etwas wie die Verwendung Login Android, für das ein Tag angegeben werden muss, normalerweise der Klassenname. Und es gibt wahrscheinlich andere Beispiele. Natürlich können Sie dafür ein Feld deklarieren (was üblich ist), aber das ist immer noch Kopieren und Einfügen.
user149408
1
Sie haben eines in Ihrer Live-Vorlagenlösung vergessen: Klicken Sie auf "Variablen bearbeiten" und geben Sie im Ausdruck CLASSein className(). Dadurch wird sichergestellt, dass $ CLASS $ tatsächlich durch den Klassennamen ersetzt wird, andernfalls wird es nur leer ausgegeben.
HerbCSO
122

Was das Codebeispiel in der Frage betrifft, besteht die Standardlösung darin, die Klasse explizit mit ihrem Namen zu referenzieren, und es ist sogar möglich, auf einen getClassLoader()Aufruf zu verzichten:

class MyClass {
  public static void startMusic() {
    URL songPath = MyClass.class.getResource("background.midi");
  }
}

Dieser Ansatz hat immer noch die Rückseite, dass er nicht sehr sicher gegen Kopier- / Einfügefehler ist, falls Sie diesen Code in eine Reihe ähnlicher Klassen replizieren müssen.

Und was die genaue Frage in der Überschrift betrifft, gibt es im nebenstehenden Thread einen Trick :

Class currentClass = new Object() { }.getClass().getEnclosingClass();

Es verwendet eine verschachtelte anonyme ObjectUnterklasse, um den Ausführungskontext abzurufen. Dieser Trick hat den Vorteil, dass er kopier- und einfügesicher ist ...

Vorsicht bei der Verwendung in einer Basisklasse, von der andere Klassen erben:

Es ist auch erwähnenswert, dass, wenn dieses Snippet als statische Methode einer Basisklasse geformt ist, der currentClassWert immer eine Referenz auf diese Basisklasse ist und nicht auf eine Unterklasse, die diese Methode möglicherweise verwendet.

Sergey Ushakov
quelle
23
Bei weitem meine Lieblingsantwort. NameOfClass.class ist offensichtlich, aber dazu müssen Sie den Namen Ihrer Klasse kennen. Der Trick getEnclosingClass ist nicht nur zum Einfügen von Kopien nützlich, sondern auch für statische Basisklassenmethoden, die Introspektion verwenden
Domingo Ignacio
1
Class.getResource()Kann sich übrigens etwas anders verhalten als ClassLoader.getResource(); Siehe stackoverflow.com/a/676273
Augurar
68

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

MethodHandles.lookup().lookupClass()
Zügel
quelle
Gute Lösung, wenn Sie nur den Aufruf getSimpleName () benötigen , um die Hilfe der Hauptmethode zu drucken .
Dmitrii Bulashevich
6
Ich suchte nach einer "Copy Paste" -Lösung, um Logger überall hinzuzufügen, ohne den Klassennamen jedes Mal zu ändern. Du hast es mir gegeben: private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Julien Feniou
Die beste Lösung, bei der es unterstützt wird. Der Nachteil ist, dass Android dies erst nach API 26, dh Android 8, unterstützt.
user149408
24

Ich habe selbst damit gerungen. Ein guter Trick besteht darin, den aktuellen Thread zu verwenden, um in einem statischen Kontext einen ClassLoader abzurufen. Dies funktioniert auch in einem Hadoop MapReduce. Andere Methoden funktionieren bei lokaler Ausführung, geben jedoch bei Verwendung in MapReduce einen Null-InputStream zurück.

public static InputStream getResource(String resource) throws Exception {
   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   InputStream is = cl.getResourceAsStream(resource);
   return is;
}
starkadder
quelle
15

Verwenden Sie einfach ein Klassenliteral, dh NameOfClass.class

Michael Borgwardt
quelle
11

getClass() Die Methode wird in der Objektklasse mit der folgenden Signatur definiert:

öffentliche Abschlussklasse getClass ()

Da es nicht als definiert ist static, können Sie es nicht innerhalb eines statischen Codeblocks aufrufen. Weitere Informationen finden Sie in diesen Antworten: Q1 , Q2 , Q3 .

Wenn Sie sich in einem statischen Kontext befinden, müssen Sie den Klassenliteralausdruck verwenden, um die Klasse abzurufen. Grundsätzlich müssen Sie also Folgendes tun:

Foo.class

Diese Art von Ausdruck wird als Klassenliterale bezeichnet und im Java Language Specification Book wie folgt erläutert :

Ein Klassenliteral ist ein Ausdruck, der aus dem Namen einer Klasse, einer Schnittstelle, eines Arrays oder eines primitiven Typs gefolgt von einem "." Besteht. und die Token-Klasse. Der Typ eines Klassenliteral ist Class. Das Class-Objekt wird für den benannten Typ (oder für void) ausgewertet, wie vom definierenden Klassenlader der Klasse der aktuellen Instanz definiert.

Informationen zu diesem Thema finden Sie auch in der API-Dokumentation für Class.

melihcelik
quelle
9

Versuch es

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

Oder

Thread.currentThread().getStackTrace()[2].getClassName()
Ishfaq
quelle
Das Schöne an diesem Ansatz ist, dass er auch auf Android-Versionen vor 8 (API 26) funktioniert. Beachten Sie, dass durch den Index [1]alle Protokollnachrichten unter ThreadAndroid 4.4.4 als Quelle gemeldet werden . [2]scheint sowohl auf Android als auch auf OpenJRE das richtige Ergebnis zu liefern.
user149408
0

Angenommen, es gibt eine Utility-Klasse, dann wäre der Beispielcode -

    URL url = Utility.class.getClassLoader().getResource("customLocation/".concat("abc.txt"));

CustomLocation - Wenn eine Ordnerstruktur innerhalb der Ressourcen vorhanden ist, entfernen Sie dieses Zeichenfolgenliteral.

Pravind Kumar
quelle
-2

Versuchen Sie so etwas. Für mich geht das. Logg (Klassenname)

    String level= "";

    Properties prop = new Properties();

    InputStream in =
            Logg.class.getResourceAsStream("resources\\config");

    if (in != null) {
        prop.load(in);
    } else {
        throw new FileNotFoundException("property file '" + in + "' not found in the classpath");
    }

    level = prop.getProperty("Level");
Oliver Lagerbäck Westblom
quelle
1
Ich bin mir nicht sicher, warum Sie dieselbe Antwort geben, die bereits dreimal in diesem Thread enthalten ist: Zweimal ab 2011, einmal ab 2013.
Antares42
Diese Lösung funktioniert, wenn die Klasse Logg vom Klassenladeprogramm mit Zugriff auf den Ordner "resources \\ config" geladen wird. Auf einigen Servern mit mehreren Klassenladeprogrammen (z. B. einem Klassenladeprogramm zum Laden des lib-Ordners und einem anderen zum Laden von App-Bibliotheken und -Ressourcen) ist dies nicht der Fall. Aus diesem Grund funktioniert die Lösung für diese Antwort nur manchmal zufällig!
Manuel Romeiro