Differenza tra finale ed effettivamente finale


351

Sto giocando con lambdas in Java 8 e mi sono imbattuto in avvertimento local variables referenced from a lambda expression must be final or effectively final. So che quando uso variabili all'interno della classe anonima devono essere final nella classe esterna, ma comunque - qual è la differenza tra final ed effettivamente final ?


2
Molte risposte, ma tutte in sostanza equivalgono a "nessuna differenza". ma è proprio vero? Sfortunatamente, non riesco a trovare una specifica della lingua per Java 8.
Aleksandr Dubinsky

3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis

@AleksandrDubinsky non "veramente" vero. Ho trovato un'eccezione a questa regola. Una variabile locale inizializzata con una costante non è un'espressione costante per il compilatore. Non è possibile utilizzare una tale variabile per un caso in un interruttore / caso fino a quando non si aggiunge esplicitamente la parola chiave finale. Ad esempio "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen,

Risposte:


234

... a partire da Java SE 8, una classe locale può accedere a variabili e parametri locali del blocco che li racchiude in modo definitivo o finale. Una variabile o un parametro il cui valore non viene mai modificato dopo l'inizializzazione è effettivamente finale.

Ad esempio, supponi che la variabile numberLengthnon sia dichiarata finale e aggiungi l'istruzione di assegnazione contrassegnata nel PhoneNumbercostruttore:

public class OutterClass {  

  int numberLength; // <== not *final*

  class PhoneNumber {

    PhoneNumber(String phoneNumber) {
        numberLength = 7;   // <== assignment to numberLength
        String currentNumber = phoneNumber.replaceAll(
            regularExpression, "");
        if (currentNumber.length() == numberLength)
            formattedPhoneNumber = currentNumber;
        else
            formattedPhoneNumber = null;
     }

  ...

  }

...

}

A causa di questa istruzione di assegnazione, la variabile numberLength non è più effettivamente definitiva. Di conseguenza, il compilatore Java genera un messaggio di errore simile a "Le variabili locali a cui fa riferimento una classe interna devono essere finali o effettivamente finali" in cui la classe interna PhoneNumber tenta di accedere alla variabile numberLength:

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html


68
+1 Nota: se un riferimento non viene modificato, è effettivamente definitivo anche se l'oggetto a cui viene fatto riferimento viene modificato.
Peter Lawrey,

1
@stanleyerror Questo potrebbe essere di qualche aiuto: stackoverflow.com/questions/4732544/...

1
Penso che più utile di un esempio di non efficacemente finale, sia un esempio di quando qualcosa è effettivamente definitivo. Sebbene la descrizione lo chiarisca. Il var non deve essere dichiarato definitivo se nessun codice cambia il suo valore.
Skychan,

1
L'esempio non è corretto. Questo codice si compila perfettamente (senza punti, ovviamente). Per ottenere l'errore del compilatore questo codice dovrebbe trovarsi all'interno di un metodo in modo che numberLengthdiventi una variabile locale di questo metodo.
mykola,

1
C'è un motivo per cui questo esempio è così complicato? Perché la maggior parte del codice si occupa di un'operazione regex del tutto irrilevante? E, come già detto da @mykola, manca completamente il segno relativo alla proprietà finale effettiva , poiché è rilevante solo per le variabili locali e non esiste una variabile locale in questo esempio.
Holger

131

Trovo che il modo più semplice per spiegare "efficacemente finale" sia immaginare di aggiungere il finalmodificatore a una dichiarazione variabile. Se, con questa modifica, il programma continua a comportarsi allo stesso modo, sia in fase di compilazione che in fase di esecuzione, tale variabile è effettivamente definitiva.


4
Questo è vero, purché la comprensione del "finale" di java 8 sia ben compresa. Altrimenti guarderei una variabile non dichiarata final a cui hai assegnato un compito in seguito, e erroneamente penso che non sia definitiva. Potresti dire "ovviamente" ... ma non tutti prestano la stessa attenzione alle ultime modifiche alla versione linguistica di quanto dovrebbero.
fool4jesus,

8
Un'eccezione a questa regola è che una variabile locale inizializzata con una costante non è un'espressione costante per il compilatore. Non è possibile utilizzare una tale variabile per un caso in un interruttore / caso fino a quando non si aggiunge esplicitamente la parola chiave finale. Ad esempio "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen,

2
@HennoVermeulen switch-case non fa eccezione alla regola in questa risposta. Il linguaggio specifica che case krichiede un'espressione costante che potrebbe essere una variabile costante ("Una variabile costante è una variabile finale di tipo o stringa primitiva inizializzata con un'espressione costante" JLS 4.12.4 ) che è un caso speciale di un finale variabile.
Colin D Bennett,

3
Nel mio esempio il compilatore lamenta che k non è un'espressione costante, quindi non può essere utilizzato per lo switch. Quando si aggiunge il comportamento finale, la compilazione cambia perché ora è una variabile costante e può essere utilizzata nello switch. Quindi hai ragione: la regola è ancora corretta. Semplicemente non si applica a questo esempio e non dice se k sia effettivamente definitivo o meno.
Henno Vermeulen,

36

Secondo i documenti :

Una variabile o un parametro il cui valore non viene mai modificato dopo l'inizializzazione è effettivamente finale.

Fondamentalmente, se il compilatore trova una variabile non appare in incarichi al di fuori della sua inizializzazione, allora la variabile viene considerata effettivamente definitiva .

Ad esempio, considera alcune classi:

public class Foo {

    public void baz(int bar) {
        // While the next line is commented, bar is effectively final
        // and while it is uncommented, the assignment means it is not
        // effectively final.

        // bar = 2;
    }
}

I documenti parlano di variabili locali. barnel tuo esempio non è una variabile locale, ma un campo. "Efficacemente definitivo" nel messaggio di errore come sopra non si applica affatto ai campi.
Antti Haapala,

6
@AnttiHaapala barè un parametro qui, non un campo.
peter.petrov,

30

'Efficace final' è una variabile che non darebbe l'errore del compilatore se fosse aggiunto da 'final'

Da un articolo di "Brian Goetz",

Informalmente, una variabile locale è effettivamente definitiva se il suo valore iniziale non viene mai modificato - in altre parole, dichiararla finale non causerebbe un errore di compilazione.

lambda-stato-finale- Brian Goetz


2
questa risposta è mostrata come una citazione, tuttavia non c'è un testo così preciso nell'articolo di Brian, sicuramente non la parola aggiunta . Questa è invece una citazione: informalmente, una variabile locale è effettivamente definitiva se il suo valore iniziale non viene mai modificato - in altre parole, dichiararla finale non causerebbe un errore di compilazione.
LCD l'

Dall'articolo testuale letterale: Informalmente, una variabile locale è effettivamente definitiva se il suo valore iniziale non viene mai modificato - in altre parole, dichiararla finale non causerebbe un errore di compilazione.
Ajeet Ganga,

26

Questa variabile di seguito è definitiva , quindi non possiamo cambiarne il valore una volta inizializzata. Se proviamo a ricevere un errore di compilazione ...

final int variable = 123;

Ma se creiamo una variabile come questa, possiamo cambiarne il valore ...

int variable = 123;
variable = 456;

Ma in Java 8 , tutte le variabili sono definitive per impostazione predefinita. Ma l'esistenza della seconda riga nel codice la rende non definitiva . Quindi, se rimuoviamo la seconda riga dal codice sopra, la nostra variabile è ora "effettivamente definitiva" ...

int variable = 123;

Quindi .. Qualsiasi variabile assegnata una sola e una sola volta è "effettivamente definitiva" .


Semplice come dovrebbe essere la risposta.
superigno,

@Eurig, citazione richiesta per "tutte le variabili sono definitive per impostazione predefinita".
Pacerier,

10

Una variabile è definitiva o efficacemente finale quando è inizializzato una volta e non è mai mutato nella sua classe di appartenenza. E non possiamo inizializzarlo in loop o classi interne .

Finale :

final int number;
number = 23;

Effettivamente finale :

int number;
number = 34;

Nota : Final ed Effective Final sono simili (il loro valore non cambia dopo l'assegnazione) ma solo le variabili Final effettive non vengono dichiarate con Keyword final.


7

Quando un'espressione lambda utilizza una variabile locale assegnata dal suo spazio racchiuso, esiste un'importante limitazione. Un'espressione lambda può usare solo una variabile locale il cui valore non cambia. Tale restrizione è definita " acquisizione variabile " che è descritta come; L'espressione lambda acquisisce valori, non variabili .
Le variabili locali che un'espressione lambda può usare sono conosciute come " effettivamente finali ".
Una variabile effettivamente finale è una variabile il cui valore non cambia dopo che è stato assegnato per la prima volta. Non è necessario dichiarare esplicitamente una variabile come final, anche se farlo non sarebbe un errore.
Vediamolo con un esempio, abbiamo una variabile locale i che è inizializzata con il valore 7, con l'espressione lambda stiamo provando a cambiare quel valore assegnando un nuovo valore a i. Ciò comporterà un errore del compilatore: " La variabile locale che ho definito in un ambito che racchiude deve essere finale o effettivamente definitiva "

@FunctionalInterface
interface IFuncInt {
    int func(int num1, int num2);
    public String toString();
}

public class LambdaVarDemo {

    public static void main(String[] args){             
        int i = 7;
        IFuncInt funcInt = (num1, num2) -> {
            i = num1 + num2;
            return i;
        };
    }   
}

2

L' argomento finale efficace è descritto in JLS 4.12.4 e l'ultimo paragrafo contiene una chiara spiegazione:

Se una variabile è effettivamente definitiva, l'aggiunta del modificatore finale alla sua dichiarazione non introdurrà alcun errore di compilazione. Al contrario, una variabile locale o un parametro dichiarato finale in un programma valido diventa effettivamente finale se il modificatore finale viene rimosso.


2

final è una variabile dichiarata con la parola chiave final, esempio:

final double pi = 3.14 ;

rimane finalper tutto il programma.

efficacemente finale : qualsiasi variabile locale o parametro a cui viene assegnato un valore solo una volta in questo momento (o aggiornato solo una volta). Potrebbe non rimanere efficacemente definitivo per tutto il programma. quindi ciò significa che la variabile finale effettiva potrebbe perdere la sua proprietà finale effettiva dopo il momento in cui viene assegnata / aggiornata almeno un'altra assegnazione. esempio:

class EffectivelyFinal {

    public static void main(String[] args) {
        calculate(124,53);
    }

    public static void calculate( int operand1, int operand2){   
     int rem = 0;  //   operand1, operand2 and rem are effectively final here
     rem = operand1%2  // rem lost its effectively final property here because it gets its second assignment 
                       // operand1, operand2 are still effectively final here 
        class operators{

            void setNum(){
                operand1 =   operand2%2;  // operand1 lost its effectively final property here because it gets its second assignment
            }

            int add(){
                return rem + operand2;  // does not compile because rem is not effectively final
            }
            int multiply(){
                return rem * operand1;  // does not compile because both rem and operand1 are not effectively final
            }
        }   
   }    
}

Ciò non è corretto secondo la specifica del linguaggio Java: " Ogni volta che si presenta come il lato sinistro in un'espressione di assegnazione, è sicuramente non assegnato e non assegnato definitivamente prima dell'assegnazione." Una variabile / parametro è sempre o mai efficacemente finale. Più esplicitamente, se non è possibile aggiungere la finalparola chiave a una dichiarazione senza introdurre errori di compilazione, non lo è effettivamente definitiva . È il contrario di questa affermazione: "Se una variabile è effettivamente definitiva, l'aggiunta del modificatore finale alla sua dichiarazione non introdurrà alcun errore di compilazione".
AndrewF,

I commenti nel codice di esempio non sono corretti per tutti i motivi descritti nel mio commento. "Effettivamente definitivo" non è uno stato che può cambiare nel tempo.
AndrewF

@AndrewF se non cambia nel tempo, cosa pensi che l'ultima riga non venga compilata? rem era effettivamente definitivo sulla riga 1 nel metodo di calcolo. Tuttavia, nell'ultima riga, il compilatore si lamenta che rem non è effettivamente definitivo
Il metodo scientifico

Hai ragione che è necessario rimuovere parte del codice dal blocco di codice per compilare, ma ciò non riflette il comportamento in fase di esecuzione. Al momento della compilazione, puoi decidere se una variabile è effettivamente definitiva o no - in base alle specifiche, è sempre efficacemente finale oppure mai efficacemente definitiva. Il compilatore può capire osservando staticamente come viene utilizzata la variabile in tutto il suo ambito. La proprietà non può essere acquisita o persa durante l'esecuzione del programma. Il termine è ben definito dalle specifiche - controlla le altre risposte, che lo spiegano abbastanza bene.
AndrewF,

1
public class LambdaScopeTest {
    public int x = 0;        
    class FirstLevel {
        public int x = 1;    
        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99; 

        }
    }    
}

Come altri hanno già detto, una variabile o parametro il cui valore non viene mai modificato dopo l'inizializzazione è effettivamente definitivo. Nel codice precedente, se si modifica il valore di xin classe interna, FirstLevelil compilatore restituirà il messaggio di errore:

Le variabili locali a cui fa riferimento un'espressione lambda devono essere definitive o effettivamente definitive.


1

Se potevi aggiungere il finalmodificatore a una variabile locale, era effettivamente definitivo.

Le espressioni lambda possono accedere

  • variabili statiche,

  • variabili di istanza,

  • parametri del metodo effettivamente finali e

  • variabili locali effettivamente finali.

Fonte: OCP: Oracle Study Professional Java SE 8 Programmer II Guida allo studio, Jeanne Boyarsky, Scott Selikoff

Inoltre,

Una effectively finalvariabile è una variabile il cui valore non viene mai modificato, ma non viene dichiarato con la finalparola chiave.

Fonte: A partire da Java: da Control Structures a Objects (6a edizione), Tony Gaddis

Inoltre, non dimenticare il significato di finalche è inizializzato esattamente una volta prima di essere usato per la prima volta.


0

Dichiarare una variabile finalo non dichiararla final, ma mantenerla effettivamente definitiva può comportare (dipende dal compilatore) un diverso bytecode.

Diamo un'occhiata a un piccolo esempio:

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

Il bytecode corrispondente del mainmetodo (Java 8u161 su Windows 64 bit):

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_1
       3: istore_2
       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iconst_1
       8: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      11: iload_2
      12: ifeq          22
      15: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: iload_2
      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      22: iload_2
      23: ifne          33
      26: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: iload_2
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      33: return

La tabella dei numeri di riga corrispondente:

 LineNumberTable:
   line 6: 0
   line 7: 2
   line 10: 4
   line 15: 11
   line 16: 15
   line 18: 22
   line 19: 26
   line 21: 33

Come si vede il codice sorgente a linee 12, 13, 14non appare nel bytecode. Questo perché iè truee non cambierà il suo stato. Quindi questo codice non è raggiungibile (più in questa risposta ). Per lo stesso motivo 9manca anche il codice in linea . Lo stato di inon deve essere valutato poiché è truesicuro.

D'altra parte, sebbene la variabile jsia effettivamente definitiva , non viene elaborata allo stesso modo. Non sono state applicate tali ottimizzazioni. Lo stato di jviene valutato due volte. Il bytecode è lo stesso indipendentemente jdall'essere effettivamente definitivo .


Lo considererei un'inefficienza del compilatore, e non necessariamente uno che sarebbe ancora vero nei compilatori più recenti. In una compilazione perfetta, se una variabile è effettivamente finale, genererà esattamente le stesse ottimizzazioni di un finale dichiarato. Quindi non fare affidamento sull'idea che effettivamente final sia automaticamente più lento di dichiarare qualcosa di final.
AndrewF,

@AndrewF In genere hai ragione, il comportamento potrebbe cambiare. Ecco perché ho scritto " potrebbe risultare (dipende dal compilatore) in bytecode diverso ". Solo a causa dell'ottimizzazione mancante (codice bytecode diverso) non presumo che l'esecuzione sia più lenta. Ma è ancora una differenza nel caso mostrato.
LuCio,

0

La variabile efficacemente finale è una variabile locale che è:

  1. Non definito come final
  2. Assegnato a SOLO una volta.

Mentre una variabile finale è una variabile che è:

  1. dichiarato con una finalparola chiave.

-6

Tuttavia, a partire da Java SE 8, una classe locale può accedere a variabili e parametri locali del blocco> che sono finali o effettivamente finali.

Questo non è iniziato su Java 8, lo uso da molto tempo. Questo codice utilizzava (prima di Java 8) per essere legale:

String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible 
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
         String ann = str; // <---- error, must be final (IDE's gives the hint);
         String ann = strFin; // <---- legal;
         String str = "legal statement on java 7,"
                +"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl."; 
         //we are forced to use another name than str
    }
);

2
L'affermazione si riferisce al fatto che in <Java 8 è possibile accedere solo alle final variabili, ma in Java 8 anche a quelle effettivamente definitive.
Antti Haapala,

Vedo solo codice che non funziona, indipendentemente dal fatto che tu stia utilizzando Java 7 o Java 8.
Holger
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.