Lambdas: les variables locales ont besoin des dernières variables d’instance

Dans un lambda, les variables locales doivent être définitives, mais pas les variables d’instance. Pourquoi donc

La différence fondamentale entre un champ et une variable locale est que la variable locale est copiée lorsque JVM crée une instance lambda. D’autre part, les champs peuvent être modifiés librement, car les modifications apscopes à cette classe sont également transmises à l’instance de classe externe (leur scope est la classe externe entière, comme Boris l’a souligné ci-dessous).

La façon la plus simple de penser aux classes, aux fermetures et aux labmdas anonymes provient de la perspective de la scope variable . Imaginez un constructeur de copie ajouté pour toutes les variables locales que vous transmettez à une fermeture.

Dans le document du projet lambda: Etat du Lambda v4

Sous la section 7. Capture variable , il est mentionné que ….

Nous avons l’intention d’interdire la capture de variables locales mutables. La raison en est que les idiomes comme ceci:

int sum = 0; list.forEach(e -> { sum += e.size(); }); 

sont fondamentalement sériels; il est assez difficile d’écrire des corps lambda comme celui-ci qui n’ont pas de conditions de course. À moins que nous ne soyons disposés à imposer, de préférence au moment de la compilation, qu’une telle fonction ne puisse pas échapper à son thread de capture, cette fonctionnalité risque de causer plus de problèmes qu’elle n’en résout.

Modifier :

Une autre chose à noter est que les variables locales sont transmises au constructeur de la classe interne lorsque vous y accédez dans votre classe interne, et cela ne fonctionnera pas avec la variable non finale car la valeur des variables non finales peut être modifiée après la construction.

Alors que dans le cas d’une variable d’instance, le compilateur transmet une référence de classe et de référence de classe sera utilisée pour accéder à la variable d’instance. Donc, il n’est pas nécessaire en cas de variables d’instance.

PS: Il est à noter que les classes anonymes ne peuvent accéder qu’aux variables locales finales (dans JAVA SE 7), tandis que dans Java SE 8, vous pouvez accéder aux variables finales aussi bien dans lambda que dans les classes internes.

Parce que les variables d’instance sont toujours accessibles via une opération d’access aux champs sur une référence à un object, c’est-à-dire some_expression.instance_variable . Même si vous n’y accédez pas explicitement via la notation par points, comme instance_variable , elle est implicitement traitée comme this.instance_variable (ou si vous êtes dans une classe interne accédant à la variable d’instance d’une classe externe, OuterClass.this.instance_variable , qui est sous le capot this..instance_variable ).

Ainsi, une variable d’instance n’est jamais directement accessible, et la vraie “variable” à laquelle vous accédez directement est la this (qui est “effectivement finale” car elle n’est pas assignable), ou une variable au début d’une autre expression.

Il semble que vous vous posiez des questions sur des variables que vous pouvez référencer d’un corps lambda.

De la JLS §15.27.2

Toute variable locale, paramètre formel ou paramètre d’exception utilisé mais non déclaré dans une expression lambda doit être déclaré final ou être effectivement final (§4.12.4), ou une erreur de compilation se produit lorsque l’utilisation est tentée.

Donc, vous n’avez pas besoin de déclarer les variables comme final vous suffit de vous assurer qu’elles sont “effectivement finales”. C’est la même règle que pour les classes anonymes.

Dans le livre Java 8 in Action , cette situation est expliquée comme suit:

Vous vous demandez peut-être pourquoi les variables locales ont ces ressortingctions. Premièrement, il existe une différence clé dans la manière dont les variables d’instance et les variables locales sont implémentées en arrière-plan. Les variables d’instance sont stockées sur le tas, alors que les variables locales sont stockées dans la stack. Si un lambda pouvait accéder directement à la variable locale et que le lambda était utilisé dans un thread, le thread utilisant le lambda pourrait essayer d’accéder à la variable après que le thread qui lui a atsortingbué la variable l’a libéré. Par conséquent, Java implémente l’access à une variable locale libre comme access à une copie de celle-ci plutôt que d’accéder à la variable d’origine. Cela ne fait aucune différence si la variable locale est affectée à une seule fois, d’où la ressortingction. Deuxièmement, cette ressortingction décourage également les schémas de programmation impératifs classiques (qui, comme nous l’expliquons dans les chapitres suivants, empêchent une parallélisation facile) qui modifient une variable externe.

Dans les expressions Lambda, vous pouvez utiliser efficacement les variables finales de la scope environnante. Effectivement, cela signifie qu’il n’est pas obligatoire de déclarer la variable final, mais assurez-vous de ne pas changer son état dans l’expression lambda.

Vous pouvez aussi l’utiliser dans les fermetures et utiliser “this” signifie l’object englobant, mais pas le lambda lui-même, car les fermetures sont des fonctions anonymes et ne sont pas associées à la classe.

Donc, quand vous utilisez n’importe quel champ (disons un entier Integer i;) de la classe englobante qui n’est pas déclarée finale et pas effectivement finale, cela fonctionnera quand le compilateur fera le tour pour vous et insérera “this” (this.i) .

 private Integer i = 0; public void process(){ Consumer c = (i)-> System.out.println(++this.i); c.accept(i); } 

Voici un exemple de code, car je ne m’y attendais pas non plus, je m’attendais à ne pouvoir rien modifier en dehors de mon lambda

  public class LambdaNonFinalExample { static boolean odd = false; public static void main(Ssortingng[] args) throws Exception { //boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" comstack error runLambda(() -> odd = true); System.out.println("Odd=" + odd); } public static void runLambda(Callable c) throws Exception { c.call(); } } 

Sortie: Odd = true

OUI, vous pouvez modifier les variables membres de l’instance, mais vous NE POUVEZ PAS modifier l’instance elle-même comme lorsque vous manipulez des variables .

Quelque chose comme ça comme mentionné:

  class Car { public Ssortingng name; } public void testLocal() { int theLocal = 6; Car bmw = new Car(); bmw.name = "BMW"; Stream.iterate(0, i -> i + 2).limit(2) .forEach(i -> { // bmw = new Car(); // LINE - 1; bmw.name = "BMW NEW"; // LINE - 2; System.out.println("Testing local variables: " + (theLocal + i)); }); // have to comment this to ensure it's `effectively final`; // theLocal = 2; } 

Le principe de base pour restreindre les variables locales concerne la validité des données et des calculs

Si le lambda, évalué par le second thread, avait la possibilité de muter les variables locales. Même la possibilité de lire la valeur des variables locales mutables à partir d’un thread différent introduirait la nécessité de la synchronisation ou de l’utilisation de volatile afin d’éviter de lire des données obsolètes.

Mais comme nous connaissons le but principal des lambdas

Parmi les différentes raisons à cela, la plus pressante pour la plate-forme Java est qu’elle facilite la dissortingbution du traitement des collections sur plusieurs threads .

Contrairement aux variables locales, l’ instance locale peut être mutée, car elle est partagée globalement. Nous pouvons mieux comprendre cela grâce à la différence entre le tas et la stack :

Chaque fois qu’un object est créé, il est toujours stocké dans l’espace Heap et la mémoire de la stack contient la référence. La mémoire de stack ne contient que des variables primitives locales et des variables de référence à des objects dans l’espace de tas.

Donc, pour résumer, il y a deux points qui me semblent importants:

  1. Il est vraiment difficile de rendre l’ instance définitive , ce qui peut entraîner un fardeau insensé (imaginez la classe nestede);

  2. l’instance elle-même est déjà partagée globalement et lambda est également partageable entre les threads, de sorte qu’ils peuvent fonctionner ensemble correctement car nous soaps que nous gérons la mutation et que nous voulons transmettre cette mutation;

Le point d’équilibre est clair: si vous savez ce que vous faites, vous pouvez le faire facilement mais sinon, la ressortingction par défaut aidera à éviter les bugs insidieux .

PS Si la synchronisation nécessite une mutation d’instance , vous pouvez utiliser directement les méthodes de réduction de stream ou, s’il y a un problème de dépendance dans la mutation d’instance , vous pouvez toujours utiliser thenApply ou thenCompose in Function pendant un mapping ou des méthodes similaires.

Mettre en place des concepts pour les futurs visiteurs:

Fondamentalement, tout se résume au fait que le compilateur devrait être capable de déterminer de manière déterministe que le corps de l’expression lambda ne fonctionne pas sur une copie périmée des variables .

Dans le cas de variables locales, le compilateur n’a aucun moyen de s’assurer que le corps d’expression lambda ne fonctionne pas sur une copie périmée de la variable, sauf si cette variable est finale ou définitive.

Maintenant, dans le cas de champs d’instance, lorsque vous accédez à un champ d’instance dans l’expression lambda, le compilateur appenda this à cet access de variable (si vous ne l’avez pas fait explicitement) et que this est effectivement définitif body aura toujours la dernière copie de la variable (veuillez noter que le multi-threading est actuellement hors de scope pour cette discussion). Ainsi, dans les cas d’instance, le compilateur peut dire que le corps lambda a la dernière copie de la variable d’instance, il n’est donc pas nécessaire que les variables d’instance soient définitives ou définitives. S’il vous plaît se référer à la capture d’écran d’une diapositive Oracle:

entrer la description de l'image ici

Notez également que si vous accédez à un champ d’instance dans l’expression lambda et que vous l’exécutez dans un environnement multithread, vous risquez de rencontrer des problèmes.