CodeGym /Kurse /JAVA 25 SELF /Anonyme Klassen: Unterschied zu Lambda-Ausdrücken, Beispi...

Anonyme Klassen: Unterschied zu Lambda-Ausdrücken, Beispiele

JAVA 25 SELF
Level 48 , Lektion 4
Verfügbar

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 35 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).

1
Umfrage/Quiz
Lambda-Ausdrücke, Level 48, Lektion 4
Nicht verfügbar
Lambda-Ausdrücke
Lambda-Ausdrücke
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION