Java a-t-il besoin de fermetures?

J’ai lu beaucoup de choses récemment sur la prochaine version de Java supportant éventuellement les fermetures . Je pense avoir une assez bonne compréhension de ce que sont les fermetures, mais je ne peux pas imaginer un exemple solide de la façon dont ils feraient un langage orienté object «meilleur». Quelqu’un peut-il me donner un cas d’utilisation spécifique où une fermeture serait nécessaire (ou même préférée)?

En tant que programmeur Lisp, je souhaiterais que la communauté Java comprenne la différence suivante: fonctions d’objects et de fermetures .

a) les fonctions peuvent être nommées ou anonymes . Mais ils peuvent aussi être des objects d’eux-mêmes. Cela permet aux fonctions d’être transmises sous forme d’arguments, renvoyées par des fonctions ou stockées dans des structures de données. Ceci signifie que les fonctions sont des objects de première classe dans un langage de programmation.

Les fonctions anonymes n’ajoutent pas beaucoup à la langue, elles vous permettent simplement d’écrire des fonctions plus rapidement.

b) Une fermeture est une fonction et un environnement contraignant . Les fermetures peuvent être transmises en aval (en tant que parameters) ou renvoyées vers le haut (en tant que valeurs de retour). Cela permet à la fonction de faire référence à des variables de son environnement, même si le code environnant n’est plus actif.

Si vous avez a) dans une langue, alors la question se pose: que faire à propos de b) ? Il y a des langues qui ont un) , mais pas b) . Dans le monde de la functional programming a) (fonctions) et b (fonctions comme fermetures) sont aujourd’hui la norme. Smalltalk avait un) (les blocs sont des fonctions anonymes) pendant longtemps, mais alors certains dialectes de Smalltalk ont ​​ajouté le support de b) (les blocs en tant que fermetures).

Vous pouvez imaginer que vous obtenez un modèle de programmation légèrement différent si vous ajoutez des fonctions et des fermetures au langage.

D’un sharepoint vue pragmatique, la fonction anonyme ajoute une notation courte, où vous passez ou invoquez des fonctions. Cela peut être une bonne chose.

La fermeture (fonction plus liaison) vous permet par exemple de créer une fonction ayant access à certaines variables (par exemple à une valeur de compteur). Maintenant, vous pouvez stocker cette fonction dans un object, y accéder et l’invoquer. Le contexte de l’object fonction est désormais non seulement les objects auxquels il a access, mais également les variables auxquelles il a access via les liaisons. Ceci est également utile, mais vous pouvez voir que les liaisons de variables et les access aux variables d’objects posent maintenant problème: quand doit-on définir une variable lexicale (accessible dans une fermeture) et quand doit-elle être une variable d’un object ( une fente ). Quand quelque chose doit-il être une fermeture ou un object? Vous pouvez utiliser les deux de la même manière. Un exercice de programmation habituel pour les étudiants apprenant le Scheme (un dialecte Lisp) consiste à écrire un système d’object simple à l’aide de fermetures.

Le résultat est un langage de programmation plus compliqué et un modèle d’exécution plus compliqué. Trop compliqué?

Ils ne font pas mieux un langage orienté object. Ils rendent les langages pratiques plus pratiques.

Si vous attaquez un problème avec le marteau OO – représente tout comme les interactions entre les objects -, une fermeture n’a aucun sens. Dans un langage OO basé sur la classe, les fermetures sont les arrière-salles enfumées où les choses se font mais personne n’en parle plus tard. Conceptuellement, c’est odieux.

En pratique, c’est extrêmement pratique. Je ne veux pas vraiment définir un nouveau type d’object pour tenir le contexte, établir la méthode “do stuff”, l’instancier et remplir le contexte … je veux juste dire au compilateur “regardez, voyez ce que vous voulez” J’ai access à tout de suite C’est le contexte que je veux et voici le code pour lequel je veux l’utiliser – attachez-vous à cela jusqu’à ce que j’en ai besoin.

Des choses fantastiques

La chose la plus évidente serait un pseudo-remplacement pour toutes les classes qui n’ont qu’une seule méthode appelée run () ou actionPerformed () ou quelque chose du genre. Ainsi, au lieu de créer un thread avec un Runnable intégré, vous utiliseriez plutôt une fermeture. Pas plus puissant que ce que nous avons maintenant, mais beaucoup plus pratique et concis.

Alors, avons-nous besoin de fermetures? Est-ce qu’ils seraient gentils d’avoir? Bien sûr, tant qu’ils ne se sentent pas enfoncés, comme je le crains.

Je suppose que pour les concepts de base de functional programming, vous avez besoin de fermetures. Rend le code plus élégant et composable avec le support des fermetures . J’aime aussi l’idée de faire passer des lignes de code en tant que parameters pour les fonctions.

Il existe des «fonctions d’ordre supérieur» très utiles qui peuvent effectuer des opérations sur des listes à l’aide de fermetures. Les fonctions d’ordre supérieur sont des fonctions ayant des «objects de fonction» en tant que parameters.

Par exemple, il est très courant d’appliquer une transformation à chaque élément d’une liste. Cette fonction d’ordre supérieur est communément appelée «carte» ou «collecte». (Voir l’opérateur de diffusion *. De Groovy ).

Par exemple, pour mettre chaque élément dans une liste sans les fermer, vous écrivez probablement:

List squareInts(List is){ List result = new ArrayList(is.size()); for (Integer i:is) result.add(i*i); return result; } 

En utilisant les fermetures et la carte et la syntaxe proposée, vous pouvez l’écrire comme ceci:

 is.map({Integer i => i*i}) 

(Il existe un problème de performance possible concernant la boxe des types primitifs.)

Comme l’explique Pop Catalin, il existe une autre fonction d’ordre supérieur appelée “select” ou “filter”: elle peut être utilisée pour obtenir tous les éléments d’une liste en respectant certaines conditions. Par exemple:

Au lieu de:

 void onlySsortingngsWithMoreThan4Chars(List ssortingngs){ List result = new ArrayList(str.size()); // should be enough for (Ssortingng str:ssortingngs) if (str.length() > 4) result.add(str); return result; } 

Au lieu de cela, vous pourriez écrire quelque chose comme

 ssortingngs.select({Ssortingng str => str.length() > 4}); 

en utilisant la proposition.

Vous pourriez regarder la syntaxe Groovy, qui est une extension du langage Java pour prendre en charge les fermetures dès maintenant. Voir le chapitre sur les collections du Guide de l’utilisateur Groovy pour plus d’exemples sur ce qu’il faut faire avec les fermetures.

Une remarque:

Il est peut-être nécessaire de clarifier le terme «fermeture». Ce que j’ai montré ci-dessus est ssortingctement interdit, pas de fermeture. Ce ne sont que des «objects de fonction». Une fermeture est tout ce qui peut capturer – ou «fermer» – le contexte (lexical) du code qui l’entoure. En ce sens, il existe actuellement des fermetures dans Java, à savoir des classes anonymes:

 Runnable createSsortingngPrintingRunnable(final Ssortingng str){ return new Runnable(){ public void run(){ System.out.println(str); // this accesses a variable from an outer scope } }; } 

Java n’a pas besoin de fermetures, un langage orienté object peut faire tout ce que fait une fermeture en utilisant des objects intermédiaires pour stocker l’état ou faire des actions (dans les classes internes de Java). Mais les fermetures sont souhaitables en tant que fonctionnalités car elles simplifient grandement le code et augmentent la lisibilité et par conséquent la maintenabilité du code.

Je ne suis pas un spécialiste de Java mais j’utilise C # 3.5 et les fermetures sont l’une de mes fonctionnalités préférées du langage, par exemple, prenez l’exemple suivant:

 // Example #1 with closures public IList GetFilteredCustomerList(ssortingng filter) { //Here a closure is created around the filter parameter return Customers.Where( c => c.Name.Contains(filter)).ToList(); } 

maintenant prendre un exemple équivalent qui n’utilise pas de fermetures

 //Example #2 without closures, using just basic OO techniques public IList GetFilteredCustomerList(ssortingng filter) { return new Customers.Where( new CustomerNameFiltrator(filter)); } ... public class CustomerNameFiltrator : IFilter { private ssortingng _filter; public CustomerNameFiltrator(ssortingng filter) { _filter = filter; } public bool Filter(Customer customer) { return customer.Name.Contains( _filter); } } 

Je sais que c’est C # et non Java mais l’idée est la même, les fermetures sont utiles pour la concision et rendent le code plus court et plus lisible. Dans les coulisses, les fermetures de C # 3.5 font quelque chose qui ressemble beaucoup à l’exemple # 2, ce qui signifie que le compilateur crée une classe privée en coulisse et lui transmet le paramètre ‘filter’.

Java n’a pas besoin de fermetures pour fonctionner, en tant que développeur, vous n’en avez pas besoin non plus, mais elles sont utiles et procurent des avantages, ce qui signifie qu’elles sont souhaitables dans un langage de production et que l’un des objectives est la productivité. .

J’ai lu beaucoup de choses récemment sur la prochaine version de Java supportant éventuellement les fermetures. J’ai l’impression de bien comprendre ce que sont les fermetures, mais je ne peux pas imaginer un exemple solide de la façon dont ils rendraient un langage orienté object «meilleur».

Eh bien, la plupart des gens qui utilisent le terme “fermeture” signifient réellement “object de fonction”, et dans ce sens, les objects de fonction permettent d’écrire du code plus simple dans certaines circonstances, par exemple lorsque vous avez besoin de comparateurs personnalisés.

Par exemple, en Python:

 def reversecmp(x, y): return y - x a = [4, 2, 5, 9, 11] a.sort(cmp=reversecmp) 

Cela sortinge la liste dans l’ordre inverse en passant la comparaison personnalisée functoin reversecmp. L’ajout de l’opérateur lambda rend les choses encore plus compactes:

 a = [4, 2, 5, 9, 11] a.sort(cmp=lambda x, y : y - x) 

Java n’a pas d’objects de fonction, il utilise donc des “classes de foncteurs” pour les simuler. En Java, vous effectuez l’opération équivalente en implémentant une version personnalisée de la classe Comparator et en la transmettant à la fonction de sorting:

 class ReverseComparator implements Comparator { public compare(Object x, Object y) { return (Integer) y - (Integer) x; } ... List a = Arrays.asList(4, 2, 5, 9, 11); Collections.sort(a, new ReverseComparator()); 

Comme vous pouvez le voir, cela donne le même effet que les fermetures, mais est plus maladroit et plus verbeux. Cependant, l’ajout de classes internes anonymes évite la plupart des douleurs:

 List a = Arrays.asList(4, 2, 5, 9, 11); Comparator reverse = new Comparator() { public Compare(Object x, Object y) { return (Integer) y - (Integer) x; } } Collections.sort(a, reverse); 

Je dirais donc que la combinaison de classes de foncteurs + classes internes anonymes en Java est suffisante pour compenser le manque d’objects fonctionnels réels, rendant leur ajout inutile.

Java a connu des fermetures depuis la version 1.1, d’une manière très lourde et limitée.

Ils sont souvent utiles partout où vous avez un rappel d’une certaine description. Un cas courant consiste à abstraire le stream de contrôle, en laissant le code intéressant appeler un algorithme avec une fermeture sans stream de contrôle externe.

Un exemple sortingvial est for-each (bien que Java 1.5 ait déjà cela). Bien que vous puissiez implémenter une méthode forEach en Java, elle est beaucoup trop verbeuse pour être utile.

Un exemple qui a déjà un sens avec Java existant est de mettre en œuvre l’idiome “execute around”, par lequel l’acquisition et la libération des ressources sont abstraites. Par exemple, l’ouverture et la fermeture d’un fichier peuvent s’effectuer à l’intérieur de try / finally, sans que le code client doive obtenir les détails corrects.

Lorsque les fermetures arriveront enfin en Java, je me débarrasserai de toutes mes classes de comparateurs personnalisées.

 myArray.sort( (a, b) => a.myProperty().compareTo(b.myProperty() ); 

… semble beaucoup mieux que …

 myArray.sort(new Comparator() { public int compare(MyClass a, MyClass b) { return a.myProperty().compareTo(b.myProperty(); } }); 

Quelques personnes ont dit, ou implicite, que les fermetures ne sont que du sucre syntaxique – faisant ce que vous pouviez déjà faire avec des classes internes anonymes et facilitant le passage de parameters.

Ce sont des sucres syntaxiques dans le même sens que Java est un sucre syntaxique pour l’assembleur (cet “assembleur” pourrait être un bytecode, par souci d’argument). En d’autres termes, ils élèvent leur niveau d’abstraction, et c’est un concept important.

Les fermetures promeuvent le concept de la fonction comme object vers une entité de première classe – une qui augmente l’expressivité du code, plutôt que de l’encombrer avec encore plus de standard.

Un exemple qui me tient à cœur a déjà été mentionné par Tom Hawtin – l’implémentation de l’idiome Execute Around, qui est à peu près le seul moyen d’intégrer le RAII à Java. J’ai écrit un article sur ce sujet il y a quelques années, lorsque j’ai entendu pour la première fois les fermetures.

Ironiquement, je pense que la raison même pour laquelle les fermetures seraient bonnes pour Java (plus d’expressivité avec moins de code) est peut-être ce qui secoue de nombreux défenseurs de Java. Java a une mentalité de “épeler tout le long chemin”. Cela et le fait que les fermetures sont un clin d’œil à une façon plus fonctionnelle de faire les choses – que je vois aussi comme une bonne chose, mais peuvent affaiblir le message purement OO que beaucoup dans la communauté Java chérissent.

J’ai beaucoup réfléchi au sujet de cette question très intéressante ces derniers jours. Tout d’abord, si j’ai bien compris, Java a déjà une notion de base des fermetures (définies par des classes anonymes) mais la nouvelle fonctionnalité qui va être introduite est la prise en charge des fermetures basées sur des fonctions anonymes.

Cette extension rendra le langage plus expressif, mais je ne suis pas sûr que cela corresponde vraiment au rest de la langue. Java a été conçu comme un langage orienté object sans support pour la functional programming: la nouvelle sémantique sera-t-elle facile à comprendre? Java 6 n’a même pas de fonctions, Java 7 aura-t-il des fonctions anonymes mais pas de fonctions “normales”?

Mon impression est que, à mesure que les nouveaux styles de programmation ou les nouveaux paradigmes, comme la functional programming, deviennent plus populaires, tout le monde veut les utiliser dans leur langage préféré. Cela est compréhensible: on veut continuer à utiliser un langage avec lequel ils sont familiers tout en adoptant de nouvelles fonctionnalités. Mais de cette manière, une langue peut devenir vraiment complexe et perdre sa cohérence.

Donc, mon attitude en ce moment est de restr fidèle à Java 6 pour la POO (j’espère que Java 6 sera encore supporté pendant un certain temps) et, au cas où je serais vraiment intéressé par OOP + FP, Scala (Scala a été définie comme multi- paradigme dès le départ et peut être bien intégrée à Java) plutôt que de passer à Java 7.

Je pense que Java doit son succès au fait qu’il combine un langage simple avec des bibliothèques et des outils très puissants, et je ne pense pas que les nouvelles fonctionnalités telles que les fermetures en feront un meilleur langage de programmation.

Maintenant que JDK8 est sur le point d’être publié, de plus amples informations sont disponibles pour enrichir les réponses à cette question.

Bria Goetz, architecte de langage chez Oracle, a publié une série d’articles (encore des brouillons) sur l’état actuel des expressions lambda en Java. Il couvre également les fermetures, car ils prévoient de les publier dans le prochain JDK, qui devrait être achevé vers janvier 2013 et devrait être publié vers le milieu de l’année 2013.

  • L’état de Lambda : dans la première page ou deux, cet article tente de répondre à la question présentée ici. Bien que je trouve toujours cela court dans les arguments, mais il est plein d’exemples.
  • L’état de Lambda – Édition Bibliothèques : c’est aussi très intéressant car il couvre des avantages comme l’évaluation paresseuse et le parallélisme.
  • La traduction des expressions lambda : ce qui explique en gros le processus de désabusage effectué par le compilateur Java.

En tant que développeur java qui essaie de s’enseigner à devenir un meilleur programmeur, je voudrais dire que j’aimerais voir la proposition de Josh Block pour les fermetures implémentée. Je me retrouve à utiliser des classes internes anonymes pour exprimer des choses comme quoi faire avec chaque élément d’une liste lors de l’agrégation de certaines données. Ce serait bien de représenter cela comme une fermeture, au lieu d’avoir à créer une classe abstraite.

Les fermetures dans un langage impératif (exemples: JavaScript, C #, l’actualisation C ++ à venir) ne sont pas les mêmes que les classes internes anonymes. Ils doivent pouvoir capturer des références modifiables à des variables locales. Les classes internes de Java ne peuvent capturer que les variables final locales.

Presque toutes les fonctionnalités linguistiques peuvent être critiquées comme non essentielles:

  • for , bien while , do n’est que du sucre syntaxique sur goto / if .
  • Les classes internes sont le sucre syntaxique par rapport aux classes avec un champ pointant vers la classe externe.
  • Les génériques sont des sucres syntaxiques plutôt que des moulages.

Exactement le même argument “non essentiel” aurait dû bloquer l’inclusion de toutes les fonctionnalités ci-dessus.

Exemples de fermeture Java

Non seulement ce benjismith, mais j’aime comment tu peux juste faire …

myArray.sort {it.myProperty}

Vous avez seulement besoin du comparateur plus détaillé que vous avez montré lorsque la comparaison en langage naturel de la propriété ne correspond pas à vos besoins.

J’adore cette fonctionnalité.

Qu’en est-il de la lisibilité et de la maintenabilité … les fermetures à une ligne sont plus difficiles à comprendre et à déboguer, imo Software a une durée de vie prolongée et permet aux gens d’avoir une connaissance rudimentaire du langage … pour une maintenance facile … Vous ne disposez généralement pas d’une application logicielle après sa sortie …

Vous voudrez peut-être regarder Groovy, un langage principalement compatible avec Java et s’exécutant sur le JRE, mais prenant en charge les fermetures.

L’absence de liaison dans la fonction anonyme [c’est-à-dire si les variables (et les arguments de méthode s’il y a une méthode englobante) du contexte externe sont déclarées finales alors disponibles mais pas autrement], je ne comprends pas bien ce que cette ressortingction achète .

J’utilise “final” à profusion de toute façon. Donc, si mon intention était d’utiliser les mêmes objects à l’intérieur de la fermeture, je déclarerais effectivement ces objects finaux dans la scope englobante. Mais ce qui ne serait pas correct en laissant la “fermeture [java aic]” obtenir simplement une copie de la référence comme si elle était passée par un constructeur (eh bien c’est en fait comme ça que ça se passe).

Si la fermeture veut écraser la référence, que ce soit; il le fera sans changer la copie que voit la scope englobante.

Si nous soutenons que cela conduirait à un code illisible (par exemple, ce n’est peut-être pas simple de voir la référence de l’object au moment de l’appel du constructeur), alors pourquoi ne pas rendre la syntaxe moins détaillée? Scala? Sensationnel?