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 3–5 righe, la leggibilità ne risente. Meglio estrarre il codice in un metodo separato o usare una classe anonima (se serve stato/più metodi).
GO TO FULL VERSION