Java Language Fermetures Java avec des expressions lambda.


Exemple

Une fermeture lambda est créée lorsqu'une expression lambda référence les variables d'une étendue englobante (globale ou locale). Les règles pour cela sont les mêmes que pour les méthodes en ligne et les classes anonymes.

Les variables locales d'une étendue englobante utilisées dans un lambda doivent être final . Avec Java 8 (la première version prenant en charge lambdas), il n'est pas nécessaire de les déclarer final dans le contexte extérieur, mais elles doivent être traitées de cette manière. Par exemple:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

Ceci est légal tant que la valeur de la variable n n'est pas modifiée. Si vous essayez de changer la variable, à l'intérieur ou à l'extérieur du lambda, vous obtiendrez l'erreur de compilation suivante:

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

Par exemple:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

S'il est nécessaire d'utiliser une variable variable dans un lambda, l'approche normale consiste à déclarer une copie final de la variable et à utiliser la copie. Par exemple

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

Naturellement, le corps du lambda ne voit pas les modifications apportées à la variable d'origine.

Notez que Java ne prend pas en charge les fermetures réelles. Un lambda Java ne peut pas être créé d'une manière qui lui permet de voir les changements dans l'environnement dans lequel il a été instancié. Si vous souhaitez implémenter une fermeture qui observe ou modifie son environnement, vous devez la simuler en utilisant une classe régulière. Par exemple:

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

L'exemple ci-dessus ne sera pas compilé pour les raisons évoquées précédemment. Nous pouvons contourner l'erreur de compilation comme suit:

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

Le problème est que cela rompt le contrat de conception de l'interface IntUnaryOperator qui stipule que les instances doivent être fonctionnelles et sans état. Si une telle fermeture est transmise à des fonctions intégrées acceptant des objets fonctionnels, elle est susceptible de provoquer des pannes ou un comportement erroné. Les fermetures qui encapsulent un état mutable doivent être implémentées comme des classes régulières. Par exemple.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}