Storia della questione:
Fino a Java 8, per utilizzare variabili dal contesto esterno in una classe interna o in una classe anonima, queste variabili dovevano essere dichiarate come final. In Java 8, questo requisito è stato allentato: ora una variabile può non essere dichiarata come final, se di fatto non viene modificata (effectively final).
Problema:
Le espressioni lambda e le classi interne utilizzano chiusure su variabili del blocco esterno. Tuttavia, se tali variabili vengono modificate, si genera confusione e comportamento errato: non è possibile capire quale valore utilizzare.
Soluzione:
Il compilatore consente di utilizzare una variabile in una classe interna o in una lambda solo se è effectively final — cioè non è mai stata modificata dopo l'inizializzazione, anche se non è dichiarata esplicitamente come final.
Esempio di codice:
public void demo() { int x = 10; Runnable r = () -> System.out.println(x); // x — effectively final r.run(); }
Se si cerca di modificare x:
public void demo() { int x = 10; x = 20; // ora x non è più effectively final Runnable r = () -> System.out.println(x); // Errore di compilazione }
Caratteristiche chiave:
È possibile utilizzare oggetti mutabili, se il riferimento stesso è effectively final?
Sì, se il riferimento non cambia, ma l'oggetto a cui fa riferimento viene modificato — questo è consentito. Ad esempio,
List<String> list = new ArrayList<>(); list.add("A"); Runnable r = () -> System.out.println(list.get(0)); // OK list = new ArrayList<>(); // Se così, ci sarà un errore di compilazione
È possibile dichiarare una variabile come final, e poi ancora modificare il contenuto dell'oggetto?
Sì. final si riferisce al riferimento, non al contenuto dell'oggetto. Modificare lo stato dell'oggetto attraverso il riferimento è consentito. Ad esempio,
final List<Integer> nums = new ArrayList<>(); nums.add(5); // OK nums = new ArrayList<>(); // Errore
È possibile utilizzare le variabili-parametri del metodo (parameters) nelle lambda?
Sì, se sono anch'esse effectively final — cioè non vengono modificate all'interno del metodo dopo l'inizializzazione.
Nel metodo vengono utilizzate variabili che vengono accidentalmente sovrascritte dopo la creazione della lambda, risultando in un errore di compilazione e causando una perdita di tempo per capire la causa.
Pro:
Contro:
Lo sviluppatore utilizza un valore incrementabile attraverso AtomicInteger (o un altro oggetto holder), che conserva il riferimento, non il valore, garantendo il corretto funzionamento delle lambda, anche se è necessario modificare il contatore all'interno dell'espressione lambda.
Pro:
Contro: