CodeGym /Corsi /JAVA 25 SELF /Classi anonime: differenze rispetto alle lambda, esempi

Classi anonime: differenze rispetto alle lambda, esempi

JAVA 25 SELF
Livello 48 , Lezione 4
Disponibile

1. Approfondiamo le classi anonime

Una classe anonima è una sottoclasse senza nome o un’implementazione di interfaccia creata direttamente nel punto d’uso. Prima dell’arrivo delle lambda (Java 8) era il modo più comodo per un’implementazione «usa e getta» di un’interfaccia o di una classe astratta.

Un classico:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Ciao da una classe anonima!");
    }
};
r.run();

Qui abbiamo dichiarato e contemporaneamente implementato l’interfaccia Runnable — senza file separato e senza nome di classe. Tali implementazioni erano spesso usate per gestori di eventi, comparatori, thread e altri casi in cui serve fornire rapidamente un comportamento.

Se la lambda è un’«espressione al volo», la classe anonima è un «piccolo attore senza nome»: recita una parte episodica e scompare.

2. Confronto con le espressioni lambda

Sintassi

Classe anonima:

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

Espressione lambda:

Comparator<String> comp = (a, b) -> a.length() - b.length();

La differenza è evidente: la lambda è più compatta — non serve esplicitare i tipi, il nome del metodo e le graffe in più se l’azione è semplice.

Funzionalità

  • Classe anonima — un oggetto a tutti gli effetti. È possibile dichiarare campi, metodi aggiuntivi, eseguire override dei metodi di Object (toString, equals, ecc.).
  • Espressione lambda — implementazione di un singolo metodo astratto di un’interfaccia funzionale. All’interno non si possono dichiarare campi propri o metodi aggiuntivi.

Quando scegliere cosa?

  • Lambda — quando serve implementare in modo conciso un solo metodo di un’interfaccia funzionale.
  • Classe anonima — quando serve:
    • implementare più metodi (ad esempio di una classe astratta);
    • dichiarare campi per mantenere stato;
    • sovrascrivere i metodi di Object (ad esempio, toString);
    • sfruttare peculiarità di ereditarietà/accesso (ad esempio ai membri protected della superclasse).

3. Ambito e parola chiave this

Qui si nasconde un tranello frequente:

  • nella classe anonima this si riferisce all’istanza della classe anonima;
  • nella lambda this si riferisce alla classe esterna in cui la lambda è dichiarata.

Esempio: confrontiamo il comportamento

public class Outer {
    String name = "Classe esterna";

    void test() {
        Runnable anon = new Runnable() {
            String name = "Classe anonima";
            @Override
            public void run() {
                System.out.println(this.name); // "Classe anonima"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "Classe esterna"

        anon.run();
        lambda.run();
    }
}

Output:

Classe anonima
Classe esterna

Nella classe anonima this punta alla stessa classe anonima (si prende il suo campo name). Nella lambda this è Outer.

4. Quando usare le classi anonime?

Se serve implementare più di un metodo

Una lambda funziona solo con interfacce funzionali (esattamente un metodo astratto). Se l’interfaccia/la classe astratta richiede di implementare più metodi — serve una classe anonima.

abstract class Animal {
    abstract void say();
    abstract void jump();
}

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("Miao!");
    }
    @Override
    void jump() {
        System.out.println("Salto!");
    }
};

Se serve mantenere stato (campi)

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void run() {
        counter++;
        System.out.println("Chiamato " + counter + " volta(e)");
    }
};
r.run(); // Chiamato 1 volta(e)
r.run(); // Chiamato 2 volta(e)

Se è necessario sovrascrivere i metodi Object

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "Comparatore per lunghezza della stringa";
    }
};
System.out.println(comp); // Comparatore per lunghezza della stringa

5. Esempi: Comparator e Runnable — lambda vs classe anonima

Ordinare le stringhe per lunghezza

Classe anonima:

List<String> words = Arrays.asList("gatto", "elefante", "topo", "tigre");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

Espressione lambda:

List<String> words = Arrays.asList("gatto", "elefante", "topo", "tigre");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

Il risultato è lo stesso, ma il codice con la lambda è più corto e più leggibile.

Runnable: avvio di un thread

Classe anonima:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread tramite classe anonima");
    }
});
t1.start();

Espressione lambda:

Thread t2 = new Thread(() -> System.out.println("Thread tramite lambda"));
t2.start();

Classe anonima con campi

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void run() {
        count++;
        System.out.println("Chiamato " + count + " volta(e)");
    }
};
r.run(); // Chiamato 1 volta(e)
r.run(); // Chiamato 2 volta(e)

Con una lambda non si può — non è possibile dichiarare un campo.

6. Particolarità: ambito, variabili e final

Sia nelle classi anonime sia nelle espressioni lambda, le variabili locali del metodo esterno si possono usare solo se sono final o «effettivamente final» (non cambiano dopo l’inizializzazione). Ma c’è una sfumatura con i nomi:

  • nella classe anonima si può dichiarare una variabile con lo stesso nome di quella nell’ambito esterno («shadowing»);
  • nella lambda non si può: il nome non deve confliggere con quello della variabile esterna.

Esempio:

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void run() {
        int x = 20; // OK: fa shadowing della variabile esterna
        System.out.println(x); // 20
    }
};
r.run();

Runnable l = () -> {
    // int x = 30; // Errore di compilazione: variabile già definita
    System.out.println(x); // 10
};
l.run();

7. Quando la lambda è preferibile e quando la classe anonima è insostituibile?

Le espressioni lambda sono la scelta giusta se:

  • serve implementare una funzione breve per un’interfaccia funzionale;
  • non è necessario mantenere stato;
  • non occorre sovrascrivere i metodi Object;
  • l’implementazione è «qui e ora» e semplice.

La classe anonima è necessaria se:

  • bisogna implementare un’interfaccia con più metodi o una classe astratta;
  • serve dichiarare campi o metodi aggiuntivi;
  • è necessario sovrascrivere toString, equals, hashCode;
  • serve accesso ai membri protected della superclasse.

8. Pratica: confronto su esempi

Esercizio 1: filtrare una lista tramite Predicate

Classe anonima:

List<String> animals = Arrays.asList("gatto", "elefante", "ape", "tigre");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [gatto, elefante, tigre]

Espressione lambda:

List<String> animals = Arrays.asList("gatto", "elefante", "ape", "tigre");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [gatto, elefante, tigre]

Esercizio 2: confronto dell’ambito di this

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. Errori tipici nel lavoro con classi anonime e espressioni lambda

Errore n. 1: aspettarsi che una lambda possa implementare più metodi. La lambda funziona solo con interfacce funzionali (un metodo astratto). Se i metodi sono più di uno — usate una classe anonima.

Errore n. 2: confusione con l’ambito di this. Nella lambda this è la classe esterna, nella classe anonima — la classe anonima stessa. Per questo è facile ottenere campi e valori «non quelli giusti».

Errore n. 3: tentare di dichiarare campi in una lambda. In una lambda non si possono dichiarare campi propri — si possono solo usare variabili del contesto esterno (final/«effettivamente final»). Per lo stato usate una classe anonima.

Errore n. 4: shadowing delle variabili. In una classe anonima è possibile dichiarare una variabile locale con lo stesso nome di quella esterna — è shadowing. In una lambda non si può: il compilatore segnalerà un errore.

Errore n. 5: logica troppo complessa in una lambda. Se il corpo della lambda supera 35 righe, la leggibilità ne risente. Meglio estrarre il codice in un metodo separato o usare una classe anonima (se serve stato/più metodi).

1
Compito
JAVA 25 SELF, livello 48, lezione 4
Bloccato
Censura nello zoo digitale 🦒
Censura nello zoo digitale 🦒
1
Compito
JAVA 25 SELF, livello 48, lezione 4
Bloccato
Voci del mondo interiore 🎭
Voci del mondo interiore 🎭
1
Sondaggio/quiz
Espressioni lambda, livello 48, lezione 4
Non disponibile
Espressioni lambda
Espressioni lambda
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION