Finale vs Efficacemente Finale in Java

1. Introduzione

Una delle funzionalità più interessanti introdotte in Java 8 è effettivamente definitiva. Ci consente di non scrivere il modificatore finale per variabili, campi e parametri che vengono effettivamente trattati e utilizzati come quelli finali.

In questo tutorial, esploreremo l' origine di questa funzionalità e come viene trattata dal compilatore rispetto alla parola chiave finale . Inoltre, esploreremo una soluzione da utilizzare per quanto riguarda un caso d'uso problematico di variabili effettivamente finali.

2. Effettivamente origine finale

In termini semplici, gli oggetti oi valori primitivi sono effettivamente finali se non cambiamo i loro valori dopo l'inizializzazione . Nel caso degli oggetti, se non cambiamo il riferimento di un oggetto, allora è effettivamente definitivo, anche se si verifica un cambiamento nello stato dell'oggetto referenziato.

Prima della sua introduzione, non potevamo usare una variabile locale non finale in una classe anonima . Non è ancora possibile utilizzare variabili a cui è stato assegnato più di un valore all'interno di classi anonime, classi interne ed espressioni lambda. L'introduzione di questa funzionalità ci consente di non dover utilizzare il modificatore finale su variabili che sono effettivamente definitive, risparmiandoci alcune battiture.

Le classi anonime sono classi interne e non possono accedere a variabili finali non finali o non effettivamente finali né modificarle nei loro ambiti di inclusione come specificato da JLS 8.1.3. La stessa limitazione si applica alle espressioni lambda, poiché l'accesso può potenzialmente produrre problemi di concorrenza.

3. Finale vs Efficacemente Finale

Il modo più semplice per capire se una variabile finale è effettivamente finale è pensare se la rimozione della parola chiave finale consentirebbe al codice di compilare ed eseguire:

@FunctionalInterface public interface FunctionalInterface { void testEffectivelyFinal(); default void test() { int effectivelyFinalInt = 10; FunctionalInterface functionalInterface = () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt); } } 

La riassegnazione di un valore o la modifica della suddetta variabile effettivamente finale renderebbe il codice non valido indipendentemente da dove si verifica.

3.1. Trattamento del compilatore

JLS 4.12.4 afferma che se rimuoviamo il modificatore finale da un parametro del metodo o da una variabile locale senza introdurre errori in fase di compilazione, allora è effettivamente definitivo. Inoltre, se aggiungiamo la parola chiave finale alla dichiarazione di una variabile in un programma valido, allora è effettivamente finale.

Il compilatore Java non esegue l'ottimizzazione aggiuntiva per le variabili finali effettivamente, a differenza di quanto fa per le variabili finali .

Consideriamo un semplice esempio che dichiara due variabili String finali ma le utilizza solo per la concatenazione:

public static void main(String[] args) { final String hello = "hello"; final String world = "world"; String test = hello + " " + world; System.out.println(test); } 

Il compilatore cambierebbe il codice eseguito nel metodo principale sopra in:

public static void main(String[] var0) { String var1 = "hello world"; System.out.println(var1); }

D'altra parte, se rimuoviamo i modificatori finali , le variabili sarebbero considerate effettivamente finali, ma il compilatore non le rimuoverà poiché sono utilizzate solo per la concatenazione.

4. Modifica atomica

In genere, non è una buona pratica modificare le variabili utilizzate nelle espressioni lambda e nelle classi anonime . Non possiamo sapere come queste variabili verranno utilizzate all'interno dei blocchi del metodo. La loro modifica potrebbe portare a risultati imprevisti in ambienti multithreading.

Abbiamo già un tutorial che spiega le migliori pratiche quando si utilizzano espressioni lambda e un altro che spiega gli anti-pattern comuni quando li modifichiamo. Ma esiste un approccio alternativo che ci consente di modificare le variabili in questi casi che raggiunge la sicurezza dei thread attraverso l'atomicità.

Il pacchetto java.util.concurrent.atomic offre classi come AtomicReference e AtomicInteger . Possiamo usarli per modificare atomicamente le variabili all'interno delle espressioni lambda:

public static void main(String[] args) { AtomicInteger effectivelyFinalInt = new AtomicInteger(10); FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet; }

5. conclusione

In questo tutorial, abbiamo appreso le differenze più importanti tra le variabili finali e quelle effettivamente finali. Inoltre, abbiamo fornito un'alternativa sicura che ci consente di modificare le variabili all'interno delle funzioni lambda.