1. Wprowadzenie do klas anonimowych
Wyobraź sobie, że masz klasę Animal, która opisuje, jak zachowuje się zwierzę. Ta klasa ma metodę say(), która wypisuje "Zwierzę wydaje dźwięk". Musisz utworzyć obiekt, który będzie zachowywał się jak pies i wypisywał "Hau!", ale nie chcesz tworzyć do tego osobnego pliku Dog.java.
Tutaj z pomocą przychodzą klasy anonimowe. To klasy bez nazwy, które deklaruje się i tworzy bezpośrednio w miejscu ich użycia. 🚀 Pozwalają „w locie” dziedziczyć po klasie bez tworzenia osobnego pliku. Zamiast mnożyć wiele małych plików z klasami używanymi tylko w jednym miejscu, możesz po prostu napisać całą logikę dokładnie tam, gdzie jest potrzebna.
Jak wygląda składnia?
Składnia klas anonimowych może wydawać się nietypowa, ale w rzeczywistości jest dość prosta:
VariableType name = new TypeToInherit() {
// Tutaj piszemy ciało klasy anonimowej
// Nadpisujemy metody klasy bazowej (rodzica)
};
Przyjrzyjmy się tej składni:
- VariableType name: To zwykła deklaracja zmiennej, w której zapiszemy nasz nowy obiekt.
- new TypeToInherit(): Wygląda to jak tworzenie nowego obiektu. Zamiast jednak podawać nazwę nowej klasy, używamy nazwy klasy, po której chcemy dziedziczyć. Zauważ, że po nawiasach () nie ma średnika !
- { ... }: Tutaj otwieramy nawiasy klamrowe i zapisujemy całą logikę naszej klasy anonimowej. Możemy nadpisać metody klasy bazowej lub dodać własną logikę.
Przykład dziedziczenia po zwykłej klasie:
Wróćmy do naszego przykładu ze zwierzęciem.
class Animal {
void say() {
System.out.println("Zwierzę wydaje dźwięk");
}
}
// Tworzymy klasę anonimową, dziedzicząc po Animal
Animal dog = new Animal() {
// Nadpisujemy metodę say()
@Override
void say() {
System.out.println("Hau-hau! 🐶");
}
};
Animal cat = new Animal() {
@Override
void say() {
System.out.println("Miau-miau! 🐱");
}
};
dog.say(); // Wypisze: Hau-hau! 🐶
cat.say(); // Wypisze: Miau-miau! 🐱
W tym przykładzie utworzyliśmy dwa obiekty, dog i cat, które w istocie są klasami anonimowymi dziedziczącymi po Animal. Przy tym nie stworzyliśmy żadnego osobnego pliku.
Porównanie podejść
// Zwykły sposób (tworzymy osobną klasę)
class Dog extends Animal {
@Override
void say() { System.out.println("Hau!"); }
}
Animal dog = new Dog();
// VS
// Klasa anonimowa (wszystko w jednym miejscu)
Animal dog = new Animal() {
@Override
void say() { System.out.println("Hau!"); }
};
Wynik jest taki sam, ale klasa anonimowa jest krótsza i nie zaśmieca projektu!
2. Nazwa klasy anonimowej po kompilacji
Klasy anonimowe nie mają nazwy w kodzie źródłowym, ale kompilator Java musi je jakoś nazwać, aby utworzyć plik .class. Robi to według ściśle określonej zasady:
- Nazwa pliku klasy anonimowej składa się z nazwy klasy zewnętrznej, w której została zadeklarowana.
- Po nazwie klasy zewnętrznej dodawany jest znak dolara $.
- Następnie występuje kolejny numer klasy anonimowej w tym pliku, zaczynając od 1.
W ten sposób, jeśli nasz przykład ze zwierzętami znajduje się w pliku Main.java, to po kompilacji powstaną trzy pliki:
- Main.class
- Main$1.class (nasza klasa anonimowa dla psa)
- Main$2.class (nasza klasa anonimowa dla kota)
Jeśli klasa anonimowa jest zadeklarowana wewnątrz metody, która sama znajduje się w klasie wewnętrznej, nazwa będzie bardziej złożona, na przykład OuterClass$InnerClass$1.class.
To wewnętrzna konwencja kompilatora, o której warto wiedzieć, ale w codziennej pracy rzadko ma duże znaczenie. Najważniejsze — pamiętać, że klasa anonimowa to wciąż pełnoprawna klasa, choć bez nazwy w kodzie źródłowym.
3. Ważne cechy i ograniczenia
Klasy anonimowe to potężne narzędzie, ale mają swoje zasady.
Dostęp do zmiennych. Klasa anonimowa może używać zmiennych z otaczającej ją metody. Jednak zmienne te muszą być final lub effectively final (to znaczy ich wartość nie zmienia się po inicjalizacji).
public void doSomething() {
String greeting = "Cześć!"; // Ta zmienna jest effectively final
class OuterClass {
void greet() {
// Tworzymy klasę anonimową wewnątrz metody
new Object() {
void sayHello() {
System.out.println(greeting); // To jest dozwolone
// greeting = "Pa!"; // A to spowoduje błąd!
}
}.sayHello();
}
}
new OuterClass().greet();
}
Dlaczego tak? Ponieważ klasa anonimowa może „żyć” dłużej niż sama metoda, a gdyby mogła zmieniać zmienną, prowadziłoby to do problemów.
Brak konstruktora. Ponieważ klasa anonimowa nie ma nazwy, nie może mieć konstruktora. Możesz jednak użyć bloku inicjalizacji, aby wykonać kod podczas tworzenia obiektu:
Animal dog = new Animal() {
// Blok inicjalizacyjny
{
System.out.println("Inicjalizacja klasy anonimowej 🐶");
}
@Override
void say() {
System.out.println("Hau-hau!");
}
};
Ograniczenia. Klasy anonimowe nie mogą deklarować statycznych pól (z wyjątkiem stałych) ani metod. Zawsze tworzy się je jako część innego obiektu, dlatego nie mogą być static, public, protected ani private.
4. Przydatne szczegóły
Kiedy używać klas anonimowych
- Potrzebujesz odziedziczyć po klasie (lub zaimplementować interfejs) tylko raz.
- Implementacja jest niewielka — 1–2 metody, najwyżej kilkadziesiąt wierszy.
- Klasa jest potrzebna tylko w jednym miejscu i nie ma sensu nadawać jej nazwy.
- Chcesz uniknąć „zaśmiecania” pakietu wieloma drobnymi, jednorazowymi klasami.
Typowe scenariusze:
- Obsługa zdarzeń (GUI, Swing, Android itd.).
- Przekazywanie callbacków do metod.
- Szybka implementacja komparatorów do sortowania kolekcji.
- Tymczasowo zmodyfikowane obiekty (np. na potrzeby testów).
5. Interakcja z klasą zewnętrzną
Jeśli klasa anonimowa jest zadeklarowana wewnątrz niestatycznej metody lub bloku klasy zewnętrznej, może odwoływać się do pól i metod tej klasy zewnętrznej (w tym prywatnych!).
public class Outer {
private String secret = "Tajny tekst";
// Klasa bazowa
class Printer {
public void print() {
System.out.println("Zwykły wypis");
}
}
public void revealSecret() {
Printer p = new Printer() {
@Override
public void print() {
System.out.println("Dostęp do prywatnego: " + secret);
}
};
p.print();
}
public static void main(String[] args) {
new Outer().revealSecret();
}
}
6. Typowe błędy przy pracy z klasami anonimowymi
Błąd nr 1: próba zmiany zmiennej z otaczającej metody.
Jeśli zadeklarujesz zmienną poza klasą anonimową i spróbujesz ją zmienić po użyciu wewnątrz klasy anonimowej — kompilator zgłosi błąd. Zmienna musi być final lub effectively final (niezmieniana po inicjalizacji).
Błąd nr 2: zbyt duża klasa anonimowa.
Jeśli klasa anonimowa rozrosła się do dziesiątek linii i zawiera kilka metod — to sygnał, że warto wynieść ją do osobnej, nazwanej klasy. W przeciwnym razie kod stanie się nieczytelny.
Błąd nr 3: próba używania metod lub pól statycznych.
W klasie anonimowej nie można deklarować metod ani pól statycznych (z wyjątkiem stałych). Jeśli naprawdę jest to potrzebne — to znak, by zrobić zwykłą klasę zagnieżdżoną.
Błąd nr 4: zapomniano o zakresie widoczności.
Klasa anonimowa jest widoczna tylko w miejscu, w którym została zadeklarowana, i nie ma nazwy. Jeśli potrzebujesz wielokrotnego użycia — zadeklaruj zwykłą klasę.
GO TO FULL VERSION