Jak wywołać metodę getClass () z metody statycznej w Javie?

350

Mam klasę, która musi mieć pewne metody statyczne. Wewnątrz tych metod statycznych muszę wywołać metodę getClass (), aby wykonać następujące wywołanie:

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

Jednak Eclipse mówi mi:

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

Jaki jest właściwy sposób naprawienia tego błędu czasu kompilacji?

Rama
źródło
Używanie getResource()przed wystąpieniem klasy zdefiniowanej przez użytkownika (np. Innej niż J2SE) czasami kończy się niepowodzeniem. Problem polega na tym, że środowisko JRE będzie używać modułu ładującego klasy bootstrap na tym etapie, który nie będzie miał zasobów aplikacji na ścieżce klasy (modułu ładującego bootstrap).
Andrew Thompson

Odpowiedzi:

616

Odpowiedź

Po prostu użyj TheClassName.classzamiast getClass().

Deklarowanie rejestratorów

Ponieważ jest to tak ważne dla konkretnego przypadku użycia - aby zapewnić łatwy sposób wstawiania deklaracji dziennika - pomyślałem, że dodam swoje przemyślenia na ten temat. Struktury dziennika często oczekują, że dziennik będzie ograniczony do określonego kontekstu, powiedzmy w pełni kwalifikowana nazwa klasy. Nie można ich więc wklejać bez modyfikacji. Sugestie dotyczące deklaracji dziennika bezpiecznych dla wklejania znajdują się w innych odpowiedziach, ale mają one wady, takie jak zawyżanie kodu bajtowego lub dodanie introspekcji środowiska wykonawczego. Nie polecam tych. Kopiuj-wklej jest kwestią edytora , więc rozwiązanie edytora jest najbardziej odpowiednie.

W IntelliJ polecam dodanie szablonu na żywo:

  • Użyj „log” jako skrótu
  • Użyj private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger($CLASS$.class);jako tekstu szablonu.
  • Kliknij Edytuj zmienne i dodaj KLASĘ, używając wyrażenia className()
  • Zaznacz pola, aby sformatować i skrócić nazwy FQ.
  • Zmień kontekst na Java: deklaracja.

Teraz, jeśli wpiszesz log<tab>, automatycznie rozwinie się do

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

I automatycznie sformatuj i zoptymalizuj importowanie.

Mark Peters
źródło
6
Spowoduje to kopiowanie / wklejanie i smutne smutne wyniki, gdy niepoprawny kod działa w innej klasie.
William Entriken,
1
@WilliamEntriken: Korzystanie z anonimowych klas wewnętrznych nie jest świetnym rozwiązaniem, po prostu powoduje powiększenie kodu bajtowego dodatkowymi plikami klas, aby zaoszczędzić kilka sekund wysiłku programisty. Nie jestem pewien, co ludzie robią tam, gdzie muszą kopiować / wklejać wszędzie, ale powinno to być traktowane za pomocą edytora, a nie hakowania językowego. np. jeśli korzystasz z IntelliJ, użyj aktywnego szablonu, który może wstawić nazwę klasy.
Mark Peters
1
Naprawdę zastanawiam się, dlaczego otrzymałeś 4 głosy negatywne
Al-Mothafar
„Nie jestem pewien, co ludzie robią tam, gdzie muszą kopiować / wklejać wszędzie” - coś takiego jak używanie Logw Androidzie, które wymaga podania tagu, zwykle nazwy klasy. I są prawdopodobnie inne przykłady. Oczywiście możesz zadeklarować pole do tego (co jest powszechną praktyką), ale nadal jest to kopiowanie i wklejanie.
user149408
1
Zapomniałeś jednej rzeczy w rozwiązaniu Live Template: Kliknij „Edytuj zmienne” i w Wyrażeniu, aby CLASSwpisać className(). To sprawi, że $ CLASS $ zostanie faktycznie zastąpione nazwą klasy, w przeciwnym razie będzie po prostu puste.
HerbCSO
122

Jeśli chodzi o przykład kodu w pytaniu, standardowym rozwiązaniem jest jawne odwołanie się do klasy poprzez jej nazwę, a nawet można obejść się bez getClassLoader()wywołania:

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

To podejście wciąż ma drugą stronę, ponieważ nie jest bardzo bezpieczne przed błędami kopiowania / wklejania na wypadek, gdybyś musiał skopiować ten kod do wielu podobnych klas.

A jeśli chodzi o dokładne pytanie w nagłówku, w sąsiednim wątku jest podana sztuczka :

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

Używa zagnieżdżonej anonimowej Objectpodklasy, aby uzyskać kontekst wykonania. Zaletą tej sztuczki jest bezpieczeństwo kopiowania / wklejania ...

Zachowaj ostrożność podczas używania tego w klasie bazowej, którą inne klasy dziedziczą:

Warto również zauważyć, że jeśli ten fragment kodu jest ukształtowany jako metoda statyczna jakiejś klasy bazowej, wówczas currentClasswartość zawsze będzie odnosić się do tej klasy bazowej, a nie do dowolnej podklasy, która może korzystać z tej metody.

Siergiej Uszakow
źródło
23
Zdecydowanie moja ulubiona odpowiedź. NameOfClass.class jest oczywiste, ale wymaga znajomości nazwy twojej klasy. Sztuczka getEnclosingClass jest nie tylko przydatna do wklejania kopii, ale także przydatna do metod statycznych klas bazowych wykorzystujących introspekcję
Domingo Ignacio
1
Nawiasem mówiąc, Class.getResource()może zachowywać się nieco inaczej ClassLoader.getResource(); patrz stackoverflow.com/a/676273
sierpień
68

W Java7 + możesz to zrobić w statycznych metodach / polach:

MethodHandles.lookup().lookupClass()
Wodza
źródło
Ładne rozwiązanie, jeśli potrzebujesz tylko wywołania getSimpleName (), aby wydrukować pomoc z głównej metody.
Dmitrii Bulashevich,
6
Szukałem rozwiązania „kopiuj wklej” do dodawania rejestratora w każdym miejscu, bez zmiany nazwy klasy za każdym razem. Dałeś mi to: private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Julien Feniou
Najlepsze rozwiązanie tam, gdzie jest obsługiwane, wadą jest to, że Android nie obsługuje tego aż do API 26, tj. Android 8.
149408
24

Sam się z tym zmagałem. Fajną sztuczką jest użycie bieżącego wątku, aby uzyskać ClassLoader w kontekście statycznym. Będzie to działać również w Hadoop MapReduce. Inne metody działają podczas uruchamiania lokalnego, ale zwracają zerowy InputStream, gdy są używane w MapReduce.

public static InputStream getResource(String resource) throws Exception {
   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   InputStream is = cl.getResourceAsStream(resource);
   return is;
}
starkadder
źródło
15

Po prostu użyj literału klasowego, tj NameOfClass.class

Michael Borgwardt
źródło
11

getClass() Metoda jest zdefiniowana w klasie Object za pomocą następującego podpisu:

publiczna finałowa klasa getClass ()

Ponieważ nie jest zdefiniowany jako static, nie można wywoływać go w bloku kodu statycznego. Zobacz te odpowiedzi, aby uzyskać więcej informacji: Q1 , Q2 , Q3 .

Jeśli jesteś w kontekście statycznym, musisz użyć dosłownego wyrażenia klasy, aby uzyskać klasę, więc w zasadzie musisz zrobić:

Foo.class

Ten typ wyrażenia nazywa się literałami klas i są one wyjaśnione w Księdze specyfikacji języka Java w następujący sposób:

Literał klasowy jest wyrażeniem składającym się z nazwy klasy, interfejsu, tablicy lub typu pierwotnego, po którym następuje „.” i klasa żetonów. Typ literału klasowego to Class. Ocenia obiekt Class dla określonego typu (lub void) zdefiniowanego przez moduł ładujący definiujący klasę bieżącej instancji.

Informacje na ten temat można również znaleźć w dokumentacji API dla Class.

melihcelik
źródło
9

Spróbuj

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

Lub

Thread.currentThread().getStackTrace()[2].getClassName()
Ishfaq
źródło
Zaletą tego podejścia jest to, że będzie działać również na wersjach Androida starszych niż 8 (API 26). Uwaga: ten indeks [1]spowoduje, że wszystkie komunikaty dziennika będą zgłaszane Threadjako źródło w Androidzie 4.4.4. [2]wydaje się dawać poprawny wynik zarówno na Androidzie, jak i na OpenJRE.
user149408
0

Załóżmy, że istnieje klasa Utility, a przykładowy kod to:

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

CustomLocation - jeśli jakakolwiek struktura folderów w obrębie zasobów w przeciwnym razie usunie ten literał ciągu.

Pravind Kumar
źródło
-2

Spróbuj czegoś takiego. Mi to pasuje. Logg (nazwa klasy)

    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
źródło
1
Nie wiem, dlaczego podajesz tę samą odpowiedź, która już jest w tym wątku trzy razy: Dwa razy od 2011 r., Raz od 2013 r.
Antares42
To rozwiązanie działa, jeśli moduł Logg jest ładowany przez moduł ładujący klasy z dostępem do folderu „resources \\ config”. Na niektórych serwerach, które mają wiele modułów ładujących klasy (na przykład jeden moduł ładujący klasy, aby załadować folder lib, a inny, aby załadować biblioteki aplikacji i zasoby), to nieprawda. Z tego powodu rozwiązanie oparte na tej odpowiedzi działa czasami tylko przypadkowo!
Manuel Romeiro