Différence entre final et effectivement final

351

Je joue avec des lambdas dans Java 8 et je suis tombé sur un avertissement local variables referenced from a lambda expression must be final or effectively final. Je sais que lorsque j'utilise des variables à l'intérieur d'une classe anonyme, elles doivent être finales dans la classe externe, mais quand même - quelle est la différence entre final et effectivement final ?

alex
la source
2
Beaucoup de réponses, mais tout cela représente essentiellement «aucune différence». Mais est-ce vraiment vrai? Malheureusement, je n'arrive pas à trouver de spécification de langage pour Java 8.
Aleksandr Dubinsky
3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis
@AleksandrDubinsky n'est pas "vraiment" vrai. J'ai trouvé une exception à cette règle. Une variable locale initialisée avec une constante n'est pas une expression constante du compilateur. Vous ne pouvez pas utiliser une telle variable pour un cas dans un commutateur / cas jusqu'à ce que vous ajoutiez explicitement le mot clé final. Par exemple "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen du

Réponses:

234

... à partir de Java SE 8, une classe locale peut accéder aux variables et paramètres locaux du bloc englobant qui sont définitifs ou effectivement définitifs. Une variable ou un paramètre dont la valeur n'est jamais modifiée après son initialisation est effectivement définitif.

Par exemple, supposons que la variable numberLengthn'est pas déclarée finale et que vous ajoutez l'instruction d'affectation marquée dans le PhoneNumberconstructeur:

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;
     }

  ...

  }

...

}

En raison de cette instruction d'affectation, la variable numberLength n'est plus effectivement définitive. Par conséquent, le compilateur Java génère un message d'erreur similaire à «les variables locales référencées à partir d'une classe interne doivent être finales ou effectivement finales» où la classe interne PhoneNumber essaie d'accéder à la variable numberLength:

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

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

Suresh Atta
la source
68
+1 Remarque: si une référence n'est pas modifiée, elle est effectivement définitive même si l'objet référencé est modifié.
Peter Lawrey
1
@stanleyerror Cela pourrait être d'une certaine aide: stackoverflow.com/questions/4732544/…
1
Je pense que plus utile qu'un exemple de pas effectivement final, est un exemple de quand quelque chose est effectivement final. Bien que la description l'indique clairement. Var n'a pas besoin d'être déclaré final si aucun code ne change sa valeur.
Skychan
1
L'exemple est incorrect. Ce code compile parfaitement (sans points, bien sûr). Pour obtenir l'erreur du compilateur, ce code doit se trouver dans une méthode de sorte qu'il numberLengthdevienne une variable locale de cette méthode.
mykola
1
Y a-t-il une raison pour laquelle cet exemple est si compliqué? Pourquoi la majorité du code traite-t-il d'une opération regex totalement non pertinente? Et, comme @mykola l'a déjà dit, il manque complètement la marque concernant la propriété finale effective , car cela ne concerne que les variables locales et il n'y a pas de variable locale dans cet exemple.
Holger
131

Je trouve que le moyen le plus simple d'expliquer "effectivement final" est d'imaginer ajouter le finalmodificateur à une déclaration de variable. Si, avec ce changement, le programme continue de se comporter de la même manière, à la fois au moment de la compilation et au moment de l'exécution, alors cette variable est effectivement définitive.

Maurice Naftalin
la source
4
C'est vrai, tant que la compréhension de la "finale" de java 8 est bien comprise. Sinon, je regarderais une variable non déclarée finale à laquelle vous avez fait une affectation plus tard et penserais à tort qu'elle n'était pas définitive. Vous pourriez dire "bien sûr" ... mais tout le monde ne prête pas autant d'attention aux derniers changements de version linguistique qu'ils le devraient.
fool4jesus
8
Une exception à cette règle est qu'une variable locale initialisée avec une constante n'est pas une expression constante pour le compilateur. Vous ne pouvez pas utiliser une telle variable pour un cas dans un commutateur / cas jusqu'à ce que vous ajoutiez explicitement le mot clé final. Par exemple "int k = 1; switch (someInt) {case k: ...".
Henno Vermeulen du
2
@HennoVermeulen switch-case n'est pas une exception à la règle dans cette réponse. Le langage spécifie qu'il case knécessite une expression constante qui pourrait être une variable constante ("Une variable constante est une variable finale de type primitif ou de type String qui est initialisée avec une expression constante" JLS 4.12.4 ) qui est un cas particulier d'une finale variable.
Colin D Bennett du
3
Dans mon exemple, le compilateur se plaint que k n'est pas une expression constante et ne peut donc pas être utilisé pour le commutateur. Lors de l'ajout final, le comportement de compilation change car il s'agit désormais d'une variable constante et il peut être utilisé dans le commutateur. Vous avez donc raison: la règle est toujours correcte. Il ne s'applique tout simplement pas à cet exemple et ne dit pas si k est effectivement final ou non.
Henno Vermeulen
36

Selon les documents :

Une variable ou un paramètre dont la valeur n'est jamais modifiée après son initialisation est effectivement définitif.

Fondamentalement, si le compilateur trouve qu'une variable n'apparaît pas dans les affectations en dehors de son initialisation, alors la variable est considérée comme effectivement finale .

Par exemple, considérez une classe:

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;
    }
}
Mark Elliot
la source
Les documents parlent de variables locales. bardans votre exemple n'est pas une variable locale, mais un champ. "Effectivement final" dans le message d'erreur comme ci-dessus ne s'applique pas du tout aux champs.
Antti Haapala
6
@AnttiHaapala barest un paramètre ici, pas un champ.
peter.petrov
30

«Effectivement final» est une variable qui ne donnerait pas d'erreur de compilation si elle était ajoutée par «final»

D'après un article de «Brian Goetz»,

De manière informelle, une variable locale est effectivement finale si sa valeur initiale n'est jamais modifiée - en d'autres termes, la déclarer finale ne provoquerait pas un échec de compilation.

lambda-state-final- Brian Goetz

Ajeet Ganga
la source
2
cette réponse est présentée comme une citation, mais il n'y a pas un tel texte exact dans l'article de Brian, certainement pas le mot ajouté . Il s'agit plutôt d'une citation: informellement, une variable locale est effectivement finale si sa valeur initiale n'est jamais modifiée - en d'autres termes, la déclarer finale ne provoquerait pas un échec de compilation.
lcfd
De la copie textuelle de l'article: Informellement, une variable locale est effectivement finale si sa valeur initiale n'est jamais modifiée - en d'autres termes, la déclarer finale ne provoquerait pas un échec de compilation.
Ajeet Ganga
26

Cette variable ci-dessous est finale , nous ne pouvons donc pas changer sa valeur une fois initialisée. Si nous essayons, nous obtiendrons une erreur de compilation ...

final int variable = 123;

Mais si nous créons une variable comme celle-ci, nous pouvons changer sa valeur ...

int variable = 123;
variable = 456;

Mais en Java 8 , toutes les variables sont finales par défaut. Mais l'existence de la 2ème ligne dans le code la rend non définitive . Donc, si nous supprimons la 2ème ligne du code ci-dessus, notre variable est désormais "effectivement définitive" ...

int variable = 123;

Donc .. Toute variable assignée une seule fois est "effectivement définitive" .

Eurig Jones
la source
Aussi simple que devrait être la réponse.
superigno
@Eurig, Citation requise pour "toutes les variables sont finales par défaut".
Pacerier
10

Une variable est finale ou effectivement finale lorsqu'elle est initialisée une fois et qu'elle n'est jamais mutée dans sa classe propriétaire. Et nous ne pouvons pas l'initialiser en boucles ou en classes internes .

Finale :

final int number;
number = 23;

Effectivement final :

int number;
number = 34;

Remarque : Final et Final Final sont similaires (leur valeur ne change pas après l'affectation) mais juste que les variables finales effectives ne sont pas déclarées avec le mot-clé final.

samadadi
la source
7

Lorsqu'une expression lambda utilise une variable locale affectée à partir de son espace englobant, il existe une restriction importante. Une expression lambda peut uniquement utiliser une variable locale dont la valeur ne change pas. Cette restriction est appelée " capture variable ", qui est décrite comme; l'expression lambda capture des valeurs, pas des variables .
Les variables locales qu'une expression lambda peut utiliser sont dites " effectivement finales ".
Une variable effectivement finale est une variable dont la valeur ne change pas après sa première affectation. Il n'est pas nécessaire de déclarer explicitement une telle variable comme finale, bien que cela ne soit pas une erreur.
Voyons-le avec un exemple, nous avons une variable locale i qui est initialisée avec la valeur 7, avec dans l'expression lambda nous essayons de changer cette valeur en assignant une nouvelle valeur à i. Cela entraînera une erreur du compilateur - « La variable locale i définie dans une étendue englobante doit être finale ou effectivement finale »

@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;
        };
    }   
}
infoj
la source
2

Le sujet final efficace est décrit dans JLS 4.12.4 et le dernier paragraphe comprend une explication claire:

Si une variable est effectivement finale, l'ajout du modificateur final à sa déclaration n'introduira aucune erreur au moment de la compilation. Inversement, une variable ou un paramètre local qui est déclaré final dans un programme valide devient effectivement définitif si le dernier modificateur est supprimé.

Novdar
la source
2

final est une variable declare avec mot clé final, exemple:

final double pi = 3.14 ;

il reste tout au finallong du programme.

effectivement finale : toute variable ou paramètre local auquel une valeur n'est attribuée qu'une seule fois maintenant (ou mise à jour une seule fois). Il peut ne pas rester effectivement définitif long du programme. cela signifie donc que la variable effectivement finale peut perdre sa propriété effectivement finale après le moment où elle est affectée / mise à jour au moins une autre affectation. exemple:

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
            }
        }   
   }    
}
La méthode scientifique
la source
Cela est incorrect selon la spécification du langage Java: « Chaque fois que cela se produit comme le côté gauche dans une expression d'affectation, il est définitivement non assigné et pas définitivement assigné avant l'affectation. Une variable / paramètre est toujours ou jamais effectivement définitif. Plus explicitement, si vous ne pouvez pas ajouter le finalmot - clé à une déclaration sans introduire d'erreurs de compilation, alors ce n'est pas effectivement définitif . C'est le contraire de cette déclaration: "Si une variable est effectivement finale, l'ajout du dernier modificateur à sa déclaration n'introduira aucune erreur au moment de la compilation."
AndrewF
Les commentaires dans l'exemple de code sont incorrects pour toutes les raisons décrites dans mon commentaire. "Effectivement définitif" n'est pas un état qui peut changer avec le temps.
AndrewF
@AndrewF si cela ne change pas avec le temps, que pensez-vous que la dernière ligne ne compile pas? rem était effectivement final à la ligne 1 de la méthode de calcul. Cependant, sur la dernière ligne, le compilateur se plaint que rem n'est pas effectivement définitif
The Scientific Method
Vous avez raison de dire qu'une partie du code doit être supprimée de votre bloc de code pour être compilée, mais cela ne reflète pas le comportement d'exécution. Au moment de la compilation, vous pouvez décider si une variable est effectivement finale ou non - en fonction de la spécification, elle est toujours effectivement finale ou elle n'est jamais effectivement finale. Le compilateur peut le dire en regardant statiquement comment la variable est utilisée dans toute sa portée. La propriété ne peut pas être gagnée ou perdue pendant l'exécution du programme. Le terme est bien défini par la spécification - consultez les autres réponses, qui l'expliquent assez bien.
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; 

        }
    }    
}

Comme d'autres l'ont dit, une variable ou un paramètre dont la valeur n'est jamais modifiée après son initialisation est effectivement définitif. Dans le code ci-dessus, si vous modifiez la valeur de xdans la classe interne, FirstLevelle compilateur vous donnera le message d'erreur:

Les variables locales référencées à partir d'une expression lambda doivent être finales ou effectivement finales.

Girish
la source
1

Si vous pouviez ajouter le finalmodificateur à une variable locale, c'était effectivement définitif.

Les expressions lambda peuvent accéder

  • variables statiques,

  • variables d'instance,

  • des paramètres de méthode finaux efficaces, et

  • variables locales finales efficaces.

Source: OCP: Oracle Certified Professional Java SE 8 Programmer II Study Guide, Jeanne Boyarsky, Scott Selikoff

Aditionellement,

Une effectively finalvariable est une variable dont la valeur n'est jamais modifiée, mais elle n'est pas déclarée avec le finalmot clé.

Source: Commencer avec Java: des structures de contrôle aux objets (6e édition), Tony Gaddis

De plus, n'oubliez pas le sens de finalce qu'il est initialisé exactement une fois avant d'être utilisé pour la première fois.

snr
la source
0

Déclarer une variable finalou ne pas la déclarer final, mais la garder effectivement finale peut entraîner (dépend du compilateur) un bytecode différent.

Jetons un coup d'œil sur un petit exemple:

    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
        }
    }

Le bytecode correspondant de la mainméthode (Java 8u161 sur 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 table des numéros de ligne correspondante:

 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

Comme on le voit le code source au niveau des lignes 12, 13, 14ne figure pas dans le code d'octets. C'est parce que ic'est trueet ne changera pas son état. Ainsi, ce code est inaccessible (plus dans cette réponse ). Pour la même raison, le code à la ligne 9manque également. Il in'est pas nécessaire d'évaluer l'état de car il est truecertain.

D'un autre côté, bien que la variable jsoit effectivement finale, elle n'est pas traitée de la même manière. Il n'y a pas de telles optimisations appliquées. L'état de jest évalué deux fois. Le bytecode est le même, même s'il jest effectivement définitif .

LuCio
la source
Je considérerais cela comme une inefficacité du compilateur, et pas nécessairement celle qui serait toujours vraie dans les nouveaux compilateurs. Dans une compilation parfaite, si une variable est effectivement finale, elle générera exactement les mêmes optimisations qu'une seule finale déclarée. Ne vous fiez donc pas à l'idée qu'effectivement final est automatiquement plus lent que de déclarer quelque chose de final.
AndrewF
@AndrewF Généralement, vous avez raison, le comportement peut changer. C'est pourquoi j'ai écrit " peut résulter (dépend du compilateur) de différents bytecode ". Seulement à cause de l'optimisation manquante (code octet différent), je ne suppose pas que l'exécution sera plus lente. Mais c'est toujours une différence dans le cas illustré.
LuCio
0

La variable effectivement finale est une variable locale qui est:

  1. Non défini comme final
  2. Attribué à SEULEMENT une fois.

Alors qu'une variable finale est une variable qui est:

  1. déclaré avec un finalmot clé.
Jimmy_Rw
la source
-6

Cependant, à partir de Java SE 8, une classe locale peut accéder aux variables et paramètres locaux du bloc englobant qui sont définitifs ou effectivement définitifs.

Cela n'a pas commencé sur Java 8, je l'utilise depuis longtemps. Ce code utilisé (avant java 8) était légal:

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
    }
);
FiruzzZ
la source
2
L'instruction fait référence au fait qu'en <Java 8, seules les final variables sont accessibles, mais en Java 8 également celles qui sont effectivement finales.
Antti Haapala
Je ne vois que du code qui ne fonctionne pas, que vous utilisiez Java 7 ou Java 8.
Holger