web-dev-qa-db-fra.com

Est-ce que Java 8 prend en charge les fermetures?

Je suis confus. Je pensais que Java8 allait sortir de l'âge de pierre et commencer à supporter les lambdas/fermetures. Mais quand j'essaye:

public static void main(String[] args) {
    int number = 5;

    ObjectCallback callback = () -> {
        return (number = number + 1);
    };

    Object result = callback.Callback();
    System.out.println(result);
}

il dit que number should be effectively final. Ce n'est pas une fermeture, je pense. Cela ressemble simplement à copier l'environnement par valeur plutôt que par référence.

Question bonus!

Est-ce que Android supportera les fonctionnalités Java-8?

43
sircodesalot

Pourquoi oh pourquoi, Java. Pourquoi oh pourquoi.

Vous devriez avoir une longue discussion (privée) avec les membres de l'équipe Oracle Java Java pour la vraie réponse. (S'ils sont prêts à vous parler ...)


Mais je soupçonne que c'est une combinaison de rétrocompatibilité et de contraintes de ressources de projet. Et le fait que l'approche actuelle soit "assez bonne" d'un point de vue pragmatique.

L'implémentation de contextes de procédure en tant qu'objets de première classe (c'est-à-dire des fermetures) nécessite que la durée de vie de certaines variables locales s'étende au-delà du retour de l'appel de méthode de déclaration. Cela signifie que vous ne pouvez pas simplement les mettre sur la pile. Au lieu de cela, vous vous retrouvez dans une situation où certaines variables locales doivent être des champs d'un objet tas. Cela signifie que vous avez besoin d'un nouveau type de classe cachée OR modifications fondamentales de l'architecture JVM.

Bien qu'il soit techniquement possible d'implémenter ce genre de chose, le langage Java n'est pas un langage de "champ vert". Un changement de nature doit prendre en charge les "fermetures réelles" serait difficile:

  • Il faudrait beaucoup d'efforts de la part d'Oracle et des implémenteurs tiers pour mettre à jour toutes les chaînes d'outils. (Et nous ne parlons pas seulement de compilateurs. Il y a des débogueurs, des profileurs, des obfuscateurs, des cadres d'ingénierie de bytecode, des cadres de persistance ...)

  • Ensuite, il y a le risque que certains de ces changements aient un impact sur la compatibilité descendante pour les millions d'applications déployées existantes Java là-bas).

  • Il y a un impact potentiel sur d'autres langages, etc. qui exploitent la JVM d'une manière ou d'une autre. Par exemple, Android dépend de l'architecture JVM/des fichiers de bytecode comme "langue d'entrée" pour sa chaîne d'outils Davlik. Il existe des implémentations de langage pour Python, Ruby = et divers langages fonctionnels que le code génère pour la plate-forme JVM.


En bref, les "vraies fermetures" en Java serait une grosse proposition effrayante pour toutes les personnes concernées. Le hack des "fermetures pour les finales" est un compromis pragmatique qui fonctionne, et qui est assez bon en pratique.

Enfin, il est toujours possible que la restriction final soit supprimée dans une prochaine édition. (Je ne retiendrais pas mon souffle cependant ....)


Est-ce que Android supportera les fonctionnalités Java-8?

Il est impossible de répondre à moins que quelqu'un ait des connaissances internes crédibles. Et s'ils le faisaient, ils seraient fous de le révéler ici. Certes, Google n'a pas annoncé la prise en charge de Java 8.

Mais la bonne nouvelle est que Java 7 extensions de syntaxe sont désormais prises en charge avec KitKat et les versions correspondantes de Android Studio ou Eclipse ADT).

40
Stephen C

Vous devrez indiquer votre définition de "fermeture".

Pour moi, une "fermeture" est quelque chose (une fonction ou un objet ou une autre chose qui peut être exécutée d'une manière ou d'une autre, comme avoir des méthodes) qui capture ("se ferme") une variable locale de sa portée englobante, et qui peut utiliser cette variable dans son code, même lorsque la fonction ou la méthode de l'objet est exécutée ultérieurement, y compris lorsque la portée englobante n'existe plus. Dans différentes langues, il peut être possible de capturer une variable par valeur, ou par référence, ou les deux.

Selon cette définition, Java classes anonymes (qui existent depuis Java 1.1) sont fermetures, car elles peuvent faire référence à des variables de sa portée englobante.

Les lambdas dans Java 8 sont essentiellement un cas spécial de classes anonymes (à savoir, une classe anonyme qui implémente une interface avec exactement une méthode (une "interface fonctionnelle"), et qui n'a pas de variables d'instance, et qui ne fait pas référence à lui-même (en utilisant this explicitement ou implicitement)). Tout lambda peut être réécrit dans une expression de classe anonyme équivalente. Donc, ce qui est dit ci-dessus s'applique également aux lambdas.

Ce n'est pas une fermeture, je pense.

Eh bien, monsieur, vous avez une définition foirée de "fermeture".

13
newacct

Je pense que la restriction final a des raisons techniques. Une expression lambda prend simplement la valeur du contexte de méthode environnant car la référence vit sur la pile et ne survivra pas à la fin de la méthode.

Si vous mettez la valeur du contexte dans une référence, vous pouvez construire une "vraie" fermeture:

import Java.util.function.Supplier;

public class CreatingAClosure {

    public static void main(String[] args) {
        Supplier<Supplier<String>> mutterfunktion = () -> {
            int container[] = {0};
            return () -> {
                container[0]++;
                return "Ich esse " + container[0] + " Kuchen.";
            };
        };
        Supplier<String> essen = mutterfunktion.get();
        System.out.println(essen.get());
        System.out.println(essen.get());
        System.out.println(essen.get());
    }
}

Ausgabe:

Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 3 Kuchen.

Au lieu d'un tableau, vous pouvez prendre n'importe quelle instance appropriée de n'importe quel objet, car il vit sur le tas et seule la référence à cette instance est conservée (finale) dans l'expression lambda.

Dans ce cas, la valeur de container est incluse dans mutterfunktion. Chaque appel à mutterfunktion crée une nouvelle instance référencée.

La valeur n'est pas accessible de l'extérieur de la fonction (qui était très difficile à construire dans Java 7 et avant). En raison des expressions lambda implémentées comme références de méthode, aucune classe interne n'est impliquée dans ce exemple.

Vous pouvez également définir container dans le contexte de la méthode et vous pourrez effectuer des modifications en dehors du lambda:

public static void main(String[] args) {
    int container[] = {0};
    Supplier<String> essen = () -> {
        container[0]++;
        return "Ich esse " + container[0] + " Kuchen.";
    };
    System.out.println(essen.get());
    System.out.println(essen.get());
    container[0]++;
    System.out.println(essen.get());
}

Ausgabe:

Ich esse 1 Kuchen.
Ich esse 2 Kuchen.
Ich esse 4 Kuchen.

La réponse à votre question sera donc "oui".

11
Volker Seibt

Vous pouvez utiliser les références finales pour contourner la modification de l'état des variables déclarées dans une portée externe, mais le résultat reste le même, l'état de la portée externe des fermetures n'est pas conservé et les modifications ultérieures apportées à l'objet référencé (par une référence finale) sont vu dans la fermeture.

@Test
public void clojureStateSnapshotTest() {
    Function wrapperFunc;
    wrapperFunc = (a) -> {
        // final reference
        final WrapLong outerScopeState = new WrapLong();

        outerScopeState.aLong = System.currentTimeMillis();
        System.out.println("outer scope state BEFORE: " + outerScopeState.aLong);

        Function closure = (b) -> {
            System.out.println("closure: " + outerScopeState.aLong);
            return b;
        };

        outerScopeState.aLong = System.currentTimeMillis();
        System.out.println("outer scope state AFTER: " + outerScopeState.aLong);

        // show correct snapshot state
        closure.apply(new Object());

        return a;
    };
    // init clojure
    wrapperFunc.apply(new Object());
}

public class WrapLong {
    public long aLong = 0;
}

mais toujours amusant ...

1
DrGranit