Czy można tworzyć statyczne anonimowe klasy wewnętrzne w Javie?

123

W Javie zagnieżdżone klasy mogą być albo staticnie. Jeśli tak static, nie zawierają odniesienia do wskaźnika instancji zawierającej (nie są już nazywane klasami wewnętrznymi, nazywane są klasami zagnieżdżonymi).

Zapomnienie o utworzeniu klasy zagnieżdżonej, staticgdy nie jest ona potrzebna, może prowadzić do problemów z usuwaniem elementów bezużytecznych lub analizą ucieczki.

Czy można również stworzyć anonimową klasę wewnętrzną static? A może kompilator wykrywa to automatycznie (co może, ponieważ nie może być żadnych podklas)?

Na przykład, jeśli tworzę anonimowy komparator, prawie nigdy nie potrzebuję odniesienia do zewnątrz:

  Collections.sort(list, new Comparator<String>(){
       int compare(String a, String b){
          return a.toUpperCase().compareTo(b.toUpperCase());
       }
  }
Thilo
źródło
Jakie są problemy z „zbieraniem elementów bezużytecznych lub analizą ucieczki”, gdy zapominamy o utworzeniu statycznej klasy wewnętrznej? Myślałem, że chodzi tylko o wydajność ...
Tim Büthe,
17
Twoja instancja klasy wewnętrznej utrzymuje aktywne odwołanie do swojej instancji zewnętrznej, nawet jeśli jej nie potrzebujesz. Może to uniemożliwić zbieranie śmieci. Wyobraź sobie (wymagający dużej ilości zasobów) obiekt fabryki, który tworzy lekkie instancje czegoś. Gdy fabryka wykona swoją pracę (np. Podczas uruchamiania aplikacji), można ją zutylizować, ale to działa tylko wtedy, gdy rzeczy, które stworzyła, nie są powiązane.
Thilo,
Wiem, to jest tylko przykład, ale ponieważ jest powtarzający się, należy wspomnieć, że Collections.sort(list, String.CASE_INSENSITIVE_ORDER)działa od Java 2, przeczytaj, ponieważ istnieje API Collection…
Holger

Odpowiedzi:

138

Nie, nie możesz i nie, kompilator nie może tego rozgryźć. Dlatego FindBugs zawsze sugeruje zmianę anonimowych klas wewnętrznych na nazwane staticklasy zagnieżdżone, jeśli nie używają one swojego niejawnego thisodwołania.

Edycja: Tom Hawtin - tackline mówi, że jeśli klasa anonimowa jest tworzona w kontekście statycznym (np. W mainmetodzie), to w rzeczywistości klasa anonimowa jest static. Ale JLS nie zgadza się :

Klasa anonimowa nigdy nie jest abstract(§8.1.1.1). Klasa anonimowa jest zawsze klasą wewnętrzną (§8.1.3); nigdy nie jest static(§8.1.1, §8.5.1). Klasa anonimowa jest zawsze niejawnie final(§8.1.1.2).

Glosariusz języka Java firmy Roedy Green mówi, że fakt, że klasy anonimowe są dozwolone w kontekście statycznym, zależy od implementacji:

Jeśli chcesz zmylić tych, którzy zajmują się Twoim kodem, odkryto, że machnięcia javac.exepozwolą na anonimowe klasy w statickodzie i staticmetodach init , nawet jeśli specyfikacja języka mówi, że klasy anonimowe nigdy nie są static. Te anonimowe klasy nie mają oczywiście dostępu do pól instancji obiektu. Nie polecam tego robić. Funkcja może zostać pociągnięty w dowolnym momencie.

Edycja 2: JLS w rzeczywistości obejmuje statyczne konteksty dokładniej w §15.9.2 :

Niech C będzie tworzoną klasą, a ja będę tworzoną instancją. Jeśli C jest klasą wewnętrzną, to mogę mieć natychmiast obejmującą instancję. Bezpośrednio zamykające wystąpienie i (§8.1.3) określa się w następujący sposób.

  • Jeśli C jest klasą anonimową, to:
    • Jeśli wyrażenie tworzenia instancji klasy występuje w kontekście statycznym (§8.1.3), to i nie ma bezpośrednio otaczającej instancji.
    • W przeciwnym razie natychmiast obejmującym wystąpieniem i jest this.

Zatem klasa anonimowa w kontekście statycznym jest z grubsza równoważna staticklasie zagnieżdżonej, ponieważ nie zachowuje odniesienia do klasy otaczającej, nawet jeśli technicznie nie jest staticklasą.

Michael Myers
źródło
19
+1 dla FindBugs - każdy programista Java powinien mieć to w swojej kompilacji.
Andrew Duffy
13
Jest to bardzo niefortunne, ponieważ oznacza, że ​​możesz chcieć uniknąć tej prawie zwięzłej składni ze względu na wydajność.
Thilo
2
JLS 3rd Ed zajmuje się przypadkami klas wewnętrznych w kontekstach statycznych. Nie są statyczne w sensie JLS, ale są statyczne w sensie podanym w pytaniu.
Tom Hawtin - tackline
6
Oto przykład zależności od implementacji: ten kod drukuje trueprzy użyciu javac (sun-jdk-1.7.0_10) i falsekompilatora Eclipse.
Paul Bellora
1
@MichaelMyers Próbowałem zasymulować FindBugs ostrzegający mnie o przeprowadzeniu Anonimowego Wewnętrznego bez użycia odniesienia „this” i nic się nie dzieje. Czy możesz zademonstrować, w jaki sposób FindBugs ostrzega Cię, jak powiedziałeś na początku swojej odpowiedzi? Po prostu wklej jakiś link lub cokolwiek.
Thufir Hawat
15

Rodzaj. Anonimowa klasa wewnętrzna utworzona w metodzie statycznej będzie oczywiście faktycznie statyczna, ponieważ nie ma źródła dla zewnętrznej this.

Istnieją pewne techniczne różnice między klasami wewnętrznymi w kontekstach statycznych i statycznymi klasami zagnieżdżonymi. Jeśli jesteś zainteresowany, przeczytaj JLS 3rd Ed.

Tom Hawtin - haczyk
źródło
Właściwie to cofam; JLS nie zgadza się. java.sun.com/docs/books/jls/third%5Fedition/html/… : "Klasa anonimowa jest zawsze klasą wewnętrzną; nigdy nie jest statyczna."
Michael Myers
1
statyczne w innym sensie niż w pytaniu.
Tom Hawtin - tackline
1
Dodałem trochę wyjaśnienia.
Tom Hawtin - tackline
15

Myślę, że jest tu trochę zamieszania w nomenklaturze, co wprawdzie jest zbyt głupie i zagmatwane.

Jakkolwiek je nazwiesz, wszystkie te wzorce (i kilka odmian o różnej widoczności) są możliwe, normalna, legalna Java:

public class MyClass {
  class MyClassInside {
  }
}

public class MyClass {
  public static class MyClassInside {
  }
}

public class MyClass {
  public void method() {
    JComponent jc = new JComponent() {
      ...
    }
  }
}

public class MyClass {
  public static void myStaticMethod() {
    JComponent jc = new JComponent() {
      ...
    }
  }
}

Są one uwzględnione w specyfikacji języka (jeśli naprawdę się tym przejmujesz, zobacz sekcję 15.9.5.1, aby uzyskać informacje na temat metody statycznej).

Ale ten cytat jest po prostu błędny :

javac.exe zezwoli na anonimowe klasy w statycznym kodzie inicjującym i statycznych metodach, mimo że specyfikacja języka mówi, że klasy anonimowe nigdy nie są statyczne

Myślę, że cytowany autor myli statyczne słowo kluczowe ze statycznym kontekstem . (Trzeba przyznać, że JLS jest również nieco zagmatwany pod tym względem).

Szczerze mówiąc, wszystkie powyższe wzorce są w porządku (jakkolwiek je nazwiesz „zagnieżdżone”, „wewnętrzne”, „anonimowe” cokolwiek…). Naprawdę nikt nie zamierza nagle usunąć tej funkcjonalności w następnej wersji Javy. Szczerze!

Neil Coffey
źródło
2
„(Trzeba przyznać, że JLS również jest pod tym względem nieco zagmatwana).” Masz rację. Dziwnie brzmiało stwierdzenie, że zależy to od implementacji, ale nie przypominam sobie, abym wcześniej widział jakiekolwiek oczywiste błędy w Słowniku języka Java. Od teraz traktuję to z przymrużeniem oka.
Michael Myers
2
Właściwie nie mówimy o żadnym z wzorców. Chodzi o to, że anonimowa klasa zagnieżdżona jest statyczna. Tj. Dodaj „statyczny” pomiędzy newiw JComponenttrzecim przykładzie.
Timmmm,
Dodałem wyjaśnienie do pierwotnego pytania, aby pokazać, czego oczekujemy.
Timmmm,
@MichaelMyers, Dyktando w JLS zawsze musi być interpretowane.
Pacerier
6

Klasy wewnętrzne nie mogą być statyczne - statyczna klasa zagnieżdżona nie jest klasą wewnętrzną. Poradnik Java mówi o tym tutaj .

Andrew Duffy
źródło
1
Zaktualizowałem pytanie, odnosząc się do oficjalnej nomenklatury.
Thilo
0

anonimowe klasy wewnętrzne nigdy nie są statyczne (nie mogą deklarować metod statycznych ani nieostatecznych pól statycznych), ale jeśli są zdefiniowane w kontekście statycznym (metoda statyczna lub pole statyczne), zachowują się jako statyczne w tym sensie, że nie mogą dostęp do niestatycznych (tj. instancji) członków klasy otaczającej (jak wszystko inne z kontekstu statycznego)

Luca
źródło
-3

Uwaga dotycząca uczynienia anonimowych klas wewnętrznych statycznymi przez wywołanie ich w ramach metody statycznej.

To faktycznie nie usuwa odniesienia. Możesz to przetestować, próbując serializować klasę anonimową i nie umożliwiając serializacji otaczającej klasy.

Terra Caines
źródło
5
-1: Tworzenie anonimową klasę w metodzie statycznej faktycznie nie usunąć odniesienie do zewnętrznej klasy. Możesz to przetestować, próbując serializować klasę anonimową i nie umożliwiając serializacji otaczającej klasy. (Właśnie to zrobiłem.)
Christian Semrau