1. Wir vertiefen uns in anonyme Klassen
Eine anonyme Klasse – das ist eine namenlose Unterklasse oder Interface-Implementierung, die direkt an der Verwendungsstelle erstellt wird. Vor der Einführung von Lambdas (Java 8) war dies die bequemste Art einer „Einmal“-Implementierung eines Interfaces oder einer abstrakten Klasse.
Der Klassiker:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hallo aus der anonymen Klasse!");
}
};
r.run();
Hier deklarieren und implementieren wir das Interface Runnable direkt – ohne separate Datei und ohne Klassennamen. Solche Implementierungen wurden häufig für Event-Handler, Komparatoren, Threads und andere Aufgaben verwendet, bei denen man schnell Verhalten „einschieben“ wollte.
Wenn eine Lambda ein „Ausdruck im Vorbeigehen“ ist, dann ist eine anonyme Klasse ein „kleiner Schauspieler ohne Namen“ – spielt eine Nebenrolle und verschwindet.
2. Vergleich mit Lambda-Ausdrücken
Syntax
Anonyme Klasse:
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
Lambda-Ausdruck:
Comparator<String> comp = (a, b) -> a.length() - b.length();
Der Unterschied ist offensichtlich: Eine Lambda ist kompakter – man muss bei einfachen Aktionen nicht explizit Typen, den Methodennamen und überflüssige geschweifte Klammern angeben.
Funktionalität
- Anonyme Klasse – ein vollwertiges Objekt. Man kann Felder und zusätzliche Methoden deklarieren sowie Methoden von Object überschreiben (toString, equals usw.).
- Lambda-Ausdruck – Implementierung genau einer abstrakten Methode eines funktionalen Interfaces. Innerhalb kann man keine eigenen Felder oder zusätzlichen Methoden deklarieren.
Wann wählt man was?
- Lambda – wenn eine einzelne Methode eines funktionalen Interfaces kurz implementiert werden soll.
- Anonyme Klasse – wenn nötig ist:
- mehrere Methoden zu implementieren (z. B. bei einer abstrakten Klasse);
- Felder für Zustand zu deklarieren;
- Methoden von Object zu überschreiben (z. B. toString);
- Spezifika von Vererbung/Zugriff zu nutzen (z. B. Zugriff auf geschützte Mitglieder der Superklasse).
3. Gültigkeitsbereich und das Schlüsselwort this
Hier lauert eine häufige Falle:
- in der anonymen Klasse verweist this auf die Instanz der anonymen Klasse;
- im Lambda-Ausdruck verweist this auf die äußere Klasse, in der die Lambda deklariert ist.
Beispiel: Verhalten vergleichen
public class Outer {
String name = "Äußere Klasse";
void test() {
Runnable anon = new Runnable() {
String name = "Anonyme Klasse";
@Override
public void run() {
System.out.println(this.name); // "Anonyme Klasse"
}
};
Runnable lambda = () -> System.out.println(this.name); // "Äußere Klasse"
anon.run();
lambda.run();
}
}
Ausgabe:
Anonyme Klasse
Äußere Klasse
In der anonymen Klasse verweist this auf die anonyme Klasse selbst (es wird ihr Feld name verwendet). In der Lambda ist this die äußere Klasse Outer.
4. Wann verwendet man anonyme Klassen?
Wenn mehr als eine Methode zu implementieren ist
Eine Lambda funktioniert nur mit funktionalen Interfaces (genau eine abstrakte Methode). Wenn ein Interface/eine abstrakte Klasse mehrere Methoden verlangt – braucht man eine anonyme Klasse.
abstract class Animal {
abstract void say();
abstract void jump();
}
Animal cat = new Animal() {
@Override
void say() {
System.out.println("Miau!");
}
@Override
void jump() {
System.out.println("Hüpf!");
}
};
Wenn Zustand (Felder) gespeichert werden soll
Runnable r = new Runnable() {
int counter = 0;
@Override
public void run() {
counter++;
System.out.println("Aufgerufen " + counter + " Mal");
}
};
r.run(); // Aufgerufen 1 Mal
r.run(); // Aufgerufen 2 Mal
Wenn Methoden von Object überschrieben werden sollen
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
@Override
public String toString() {
return "Komparator nach Stringlänge";
}
};
System.out.println(comp); // Komparator nach Stringlänge
5. Beispiele: Comparator und Runnable — Lambda vs. anonyme Klasse
Strings nach Länge sortieren
Anonyme Klasse:
List<String> words = Arrays.asList("kot", "slon", "mysh", "tigr");
words.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
System.out.println(words);
Lambda-Ausdruck:
List<String> words = Arrays.asList("kot", "slon", "mysh", "tigr");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);
Das Ergebnis ist identisch, aber der Code mit Lambda ist kürzer und leichter zu lesen.
Runnable: Thread starten
Anonyme Klasse:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread mit anonymer Klasse");
}
});
t1.start();
Lambda-Ausdruck:
Thread t2 = new Thread(() -> System.out.println("Thread mit Lambda"));
t2.start();
Anonyme Klasse mit Feldern
Runnable r = new Runnable() {
int count = 0;
@Override
public void run() {
count++;
System.out.println("Aufgerufen " + count + " Mal");
}
};
r.run(); // Aufgerufen 1 Mal
r.run(); // Aufgerufen 2 Mal
In einer Lambda geht das nicht – es gibt keine Möglichkeit, ein Feld zu deklarieren.
6. Besonderheiten: Gültigkeitsbereich, Variablen und final
Sowohl in anonymen Klassen als auch in Lambda-Ausdrücken dürfen lokale Variablen der äußeren Methode nur verwendet werden, wenn sie final oder „effektiv final“ sind (nach der Initialisierung unverändert bleiben). Aber es gibt einen Namens-Nuance:
- in einer anonymen Klasse kann man eine Variable mit demselben Namen wie im äußeren Bereich deklarieren („Shadowing“);
- in einer Lambda – geht das nicht: Der Name darf nicht mit dem der äußeren Variable kollidieren.
Beispiel:
int x = 10;
Runnable r = new Runnable() {
@Override
public void run() {
int x = 20; // OK: überschattet die äußere Variable
System.out.println(x); // 20
}
};
r.run();
Runnable l = () -> {
// int x = 30; // Compilerfehler: Variable ist bereits definiert
System.out.println(x); // 10
};
l.run();
7. Wann sind Lambdas besser, und wann ist die anonyme Klasse unersetzlich?
Lambda-Ausdrücke sind die richtige Wahl, wenn:
- eine kurze Funktion für ein funktionales Interface implementiert werden soll;
- kein Zustand gespeichert werden muss;
- Methoden von Object nicht überschrieben werden müssen;
- die Implementierung „hier und jetzt“ gebraucht wird und einfach ist.
Eine anonyme Klasse ist notwendig, wenn:
- ein Interface mit mehreren Methoden oder eine abstrakte Klasse implementiert werden muss;
- Felder oder zusätzliche Methoden deklariert werden sollen;
- toString, equals, hashCode überschrieben werden müssen;
- Zugriff auf geschützte Mitglieder der Superklasse benötigt wird.
8. Praxis: Vergleich an Beispielen
Aufgabe 1: Filtern einer Liste mit Predicate
Anonyme Klasse:
List<String> animals = Arrays.asList("kot", "slon", "mysh", "tigr");
animals.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() < 4;
}
});
System.out.println(animals); // [slon, mysh, tigr]
Lambda-Ausdruck:
List<String> animals = Arrays.asList("kot", "slon", "mysh", "tigr");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [slon, mysh, tigr]
Aufgabe 2: Vergleich der this-Sichtbarkeit
public class Demo {
String name = "Demo";
void check() {
Runnable anon = new Runnable() {
String name = "Anon";
@Override
public void run() {
System.out.println(this.name); // "Anon"
}
};
Runnable lambda = () -> System.out.println(this.name); // "Demo"
anon.run();
lambda.run();
}
public static void main(String[] args) {
new Demo().check();
}
}
9. Typische Fehler beim Arbeiten mit anonymen Klassen und Lambda-Ausdrücken
Fehler Nr. 1: Die Erwartung, dass eine Lambda mehrere Methoden implementieren kann. Eine Lambda funktioniert nur mit funktionalen Interfaces (eine abstrakte Methode). Wenn es mehr Methoden sind – verwenden Sie eine anonyme Klasse.
Fehler Nr. 2: Verwechslung des Gültigkeitsbereichs von this. In einer Lambda ist this die äußere Klasse, in einer anonymen Klasse – die anonyme Klasse selbst. Dadurch kann man leicht die „falschen“ Felder und Werte erwischen.
Fehler Nr. 3: Versuch, Felder in einer Lambda zu deklarieren. In einer Lambda kann man keine eigenen Felder deklarieren – man kann nur Variablen des äußeren Kontexts verwenden (final/„effektiv final“). Für Zustand verwenden Sie eine anonyme Klasse.
Fehler Nr. 4: Shadowing von Variablen. In einer anonymen Klasse kann eine lokale Variable mit demselben Namen wie im äußeren Bereich deklariert werden – das ist Shadowing. In einer Lambda geht das nicht: Der Compiler meldet einen Fehler.
Fehler Nr. 5: Zu komplexe Logik in der Lambda. Wenn der Körper einer Lambda länger als 3–5 Zeilen wird, leidet die Lesbarkeit. Besser den Code in eine separate Methode auslagern oder eine anonyme Klasse verwenden (wenn Zustand/mehrere Methoden nötig sind).
GO TO FULL VERSION