Final vs effectivement final en Java

1. Introduction

L'une des fonctionnalités les plus intéressantes introduites dans Java 8 est effectivement définitive. Cela nous permet de ne pas écrire le modificateur final pour les variables, les champs et les paramètres qui sont effectivement traités et utilisés comme les derniers.

Dans ce didacticiel, nous explorerons l' origine de cette fonctionnalité et la manière dont elle est traitée par le compilateur par rapport au mot clé final . De plus, nous explorerons une solution à utiliser concernant un cas d'utilisation problématique de variables finales efficaces.

2. Origine effectivement finale

En termes simples, les objets ou valeurs primitives sont effectivement définitifs si l'on ne change pas leurs valeurs après l'initialisation . Dans le cas des objets, si nous ne modifions pas la référence d'un objet, alors elle est effectivement définitive - même si un changement se produit dans l'état de l'objet référencé.

Avant son introduction, nous ne pouvions pas utiliser une variable locale non finale dans une classe anonyme . Nous ne pouvons toujours pas utiliser des variables auxquelles plus d'une valeur leur est assignée dans des classes anonymes, des classes internes et des expressions lambda. L'introduction de cette fonctionnalité nous permet de ne pas avoir à utiliser le modificateur final sur des variables qui sont effectivement finales, ce qui nous évite quelques frappes.

Les classes anonymes sont des classes internes et elles ne peuvent pas accéder aux variables non finales ou non finales ou les muter dans leurs portées englobantes comme spécifié par JLS 8.1.3. La même limitation s'applique aux expressions lambda, car l'accès peut potentiellement produire des problèmes de concurrence.

3. Finale vs effectivement finale

Le moyen le plus simple de comprendre si une variable finale est effectivement finale est de penser si la suppression du mot clé final permettrait au code de se compiler et de s'exécuter:

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

La réattribution d'une valeur ou la mutation de la variable finale ci-dessus rendrait le code invalide quel que soit l'endroit où il se produit.

3.1. Traitement du compilateur

JLS 4.12.4 déclare que si nous supprimons le modificateur final d'un paramètre de méthode ou d'une variable locale sans introduire d'erreurs de compilation, alors il est effectivement définitif. De plus, si nous ajoutons le mot-clé final à la déclaration d'une variable dans un programme valide, alors il est effectivement final.

Le compilateur Java ne fait pas d'optimisation supplémentaire pour les variables finales, contrairement à ce qu'il fait pour les variables finales .

Prenons un exemple simple qui déclare deux variables String finales mais ne les utilise que pour la concaténation:

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

Le compilateur changerait le code exécuté dans la méthode principale ci-dessus en:

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

D'un autre côté, si nous supprimons les modificateurs finaux , les variables seront considérées comme effectivement finales, mais le compilateur ne les supprimera pas car elles ne sont utilisées que pour la concaténation.

4. Modification atomique

En règle générale, il n'est pas recommandé de modifier les variables utilisées dans les expressions lambda et les classes anonymes . Nous ne pouvons pas savoir comment ces variables vont être utilisées à l'intérieur des blocs de méthodes. Leur mutation peut entraîner des résultats inattendus dans les environnements multithreading.

Nous avons déjà un tutoriel expliquant les meilleures pratiques lors de l'utilisation d'expressions lambda et un autre qui explique les anti-modèles courants lorsque nous les modifions. Mais il existe une approche alternative qui nous permet de modifier les variables dans de tels cas, qui assure la sécurité des threads grâce à l'atomicité.

Le package java.util.concurrent.atomic propose des classes telles que AtomicReference et AtomicInteger . Nous pouvons les utiliser pour modifier de manière atomique des variables à l'intérieur d'expressions lambda:

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

5. Conclusion

Dans ce didacticiel, nous avons découvert les différences les plus notables entre les variables finales et effectivement finales. De plus, nous avons fourni une alternative sûre qui nous permet de modifier les variables à l'intérieur des fonctions lambda.