décembre
2009
Tel est la question que l’on peut se poser après l’annonce surprise de l’intégration des closures dans Java 7.
// function expressions
#(int i, String s) {
System.println.out(s);
return i + str.length();
}
// function expressions
#(int i, String s) (i + str.length())
// function types
#int(int, String)
Stephen Colebourne a fait un résumé des fonctionnalités des différentes propositions de closures, en les reliant avec le peu d’information obtenu lors de la conférence Devoxx et à la nouvelle proposition de Neal Gafter : Closures for Java 0.6a, qui est présenté par tous comme un document de travail tentant de faire le consensus de chaque proposition.
Voyons voir cela de plus près…
Voici les différents éléments qu’il a mit en évidence, repris par mes soins et détaillés après le tableau :
CICE | BGGA 0.5 | FCM 0.5 | CFJ 0.6a | Devoxx | |
---|---|---|---|---|---|
Littéraux pour la réflection | NON | NON | OUI | NON | ? |
Référence de méthode | NON | NON | OUI | OUI | ? |
Closures assignable aux interfaces mono-méthode | OUI | OUI | OUI | OUI | ? |
Closures indépendante des interfaces mono-méthode | NON | OUI | OUI | OUI | OUI |
Accès aux variables locales non-final | OUI | OUI | OUI | OUI | Peut-être pas |
‘this’ référence la classe englobante | NON | OUI | OUI | OUI | Probablement |
‘return’ lié à la closure | OUI | OUI* | OUI | OUI | OUI |
‘return’ lié à la méthode appellante | NON | OUI* | NON | NON | NON |
Transparence des exceptions | NON | OUI | OUI | OUI | Peut-être pas |
Type ‘Fonction’ | NON | OUI | OUI | OUI | OUI |
Structures de contrôles | NON | OUI | NON | NON | NON |
* : La proposition BGGA 0.5 permettait au mot-clef return d’avoir deux significations différentes selon le contexte. |
Littéraux pour la réflection ?
Cette notion fait partie intégrante de la proposition FCM, mais sort un peu du contexte des closures puisqu’il s’agit de définir des littéraux permettant de référencer facilement et de manière sûre des méthodes/attributs en tant qu’objet Method/Field
.
En clair le but est de pouvoir écrire quelque chose comme cela :
Method min = Math#min(int, int);
Field out = System#out;
C’est toujours incertain, mais à mon avis il y a peu de chance de voir ceci débarquer en l’état, puisqu’on perd quand même le typeage des éléments et que cela apporte donc peu d’intérêt.
Référence de méthode ?
On reprend le même principe consistant à référencer une méthode, mais ici au lieu de l’associer à un objet de type Method
, on l’associe à une closure :
#int(int,int) min = Math#min(int, int);
Ici c’est désormais nettement plus intéressant puisqu’on obtient un vrai code typesafe dès la compilation grâce à l’utilisation des closures !
Closures assignable aux interfaces mono-méthode ?
Cette notion de base reprise par toutes les propositions permet tout simplement d’assigner automatiquement une closure à une interface mono-méthode. En clair la closure implémenterait la méthode de l’interface. Ceci à condition que leurs signatures respectives soient compatibles.
ActionListener listener = #(ActionEvent event) {
// Code de traitement de l'event
};
Ceci faisait l’unanimité parmi les propositions. La seule différence venait de la définition exacte du terme interface mono-méthode
(certaines propositions incluent également les classes abstraites ne contenant qu’une seule méthode abstraite).
Rien n’a transpirer de Devoxx, mais il s’agit d’un « must-have » pour moi : cela permettrait d’utiliser les closures à la place des classes anonymes dans de nombreux cas, et donc de permettre leurs utilisations avec les APIs existantes qui n’avait pas forcément été conçus pour.
Sans cela l’utilisation des closures restera limité aux nouvelles APIs.
Closures indépendante des interfaces mono-méthode OUI
Il s’agit de la notion complètement inverse : les closures peuvent être totalement indépendante d’éventuelle interface.
En clair cela permet de définir un type closures dynamiquement :
#void() doSomething = #() { System.out.println("Hello World !"); };
doSomething.invoke();
En fait il y a peu de raison pour que cela ne soit pas mis en place avec un vrai système de closure.
La proposition CICE ne permet pas cela puisqu’il s’agit uniquement d’un sucre syntaxique pour l’écriture des classes anonymes…
Accès aux variables locales non-finales Peut-être pas
Pour rappel cela consiste tout simplement à permettre la manipulation des variables locales à une fonction depuis les closures (et les classes anonymes) sans avoir à les déclarer final
(comme c’est le cas actuellement).
Malgré que ce soit une notion commune à toutes les propositions, l’accès aux variables locales non-finales est sujets à de nombreux débats. En effet le partage d’une variable locale avec des closures (ou des classes anonymes) modifie fortement le model mémoire de Java et peut entrainer un grand nombre de cas critique ou de résultats inattendus, sans compter les problèmes de synchronisation inter-threads.
Tout ceci dépend fortement du moment où le code de la closures sera exécuté…
Bref ceci pourrait bien s’avérer contre-productif, malgré certains avantages indéniables si c’est bien utilisé.
‘this’ référence la classe englobante Probablement
Cela consiste simplement à considérer que le mot-clef this
référence le contexte de la méthode appelante plutôt que le type correspondant à la closure. Ceci permettant d’éviter d’avoir à préfixer le tout par le nom de la classe.
On est là sur un détail de l’implémentation qui n’aura pas vraiment de grosse conséquence, mais ce serait somme toute assez logique dans une closure puisqu’elle ne représente d’une portion de code…
‘return’ lié à la closure OUI
On rentre ici sur un des points important longuement débattus : le rôle exact du mot-clef return
au sein de la closures (ainsi que des mot-clefs break
et continue
).
On se dirige apparemment vers un comportement où ces mot-clefs seront lié à la closure. C’est à dire qu’il impacteront uniquement sur le code de la closure.
En clair :
- Le mot clef
return
met fin au code de la méthode représenté par la closure. - Les mots clefs
break
etcontinue
s’applique à une boucle à l’intérieur de la closure.
C’est à dire que l’on obtient un comportement similaire aux classes anonymes, et sauf gros changement on se dirige vers cette solution beaucoup plus simple à comprendre.
‘return’ lié à la méthode appellante NON
La proposition BGGA permettait également de lier les mots clefs return
, break
et continue
à la méthode appelante.
En clair l’utilisation de ces mot-clefs dans la closure s’appliquait non pas à son seul contexte, mais à celui de la méthode appelante. A condition bien sûr que la closure soit bien exécuté dans le même contexte. On parle alors d’unrestricted closure.
Bien maitrisé, cela permet d’utiliser toutes la puissance des closures, mais cela complexifie énormément leur compréhension par le plus grand nombre (je reviendrais là-dessus dans la section « Structures de contrôles » ci-dessous).
Transparence des exceptions Peut-être pas
Sous ce terme un peu bizarre se cache en fait une légère modification du traitements des exceptions dans les types paramétrés des Generics.
En fait actuellement on peut déjà utiliser les types paramétrées pour définir dynamiquement le type de l’exception :
public <E extends Exception> void method() throws E
Mais il y a une grosse limitation : le nombre de type d’exception est fixé et immuable.
Ce n’est pas gênant en soit puisque cela n’a pas vraiment d’utilité actuellement. Or avec les closures on pourrait bien avoir besoin de pouvoir typer ces différentes exceptions, au moins lors de la compilation afin que le compilateur puissent vérifier la cohérence de l’ensemble.
Par exemple on voudrais pouvoir paramétrer les exceptions selon une closure reçu en paramètre :
public <E extends Exception> void method(#void()throws E myClosure) throws E
Le problème vient du fait qu’une closure peut définir plusieurs exceptions (tout comme les méthodes standard en fait), et que du coup le paramétrage ne fonctionne plus :
#void()throws IOException|SQLException myClosure;
method(myClosure); // Erreur car E est du type IOException ou SQLException !
La solution consiste à utiliser un paramétrage spécial pour les exceptions, précédé du mot-clef throws indiquant qu’on accepte plusieurs types d’exceptions, afin de pouvoir faire correspondre la signature de la méthode paramétrée avec la closure reçu en paramètre :
public <throws E extends Exception> void method(#void()throws E myClosure) throws E
Type ‘Fonction’ OUI
Le type « Fonction » correspond tout simplement à un type représentant une closure. Il peut être utilisé à n’importe quel emplacement acceptant un type Java, et peut être défini dynamiquement afin de préciser les paramètres, le type de retour et les éventuelles exceptions de la méthode.
Pour reprendre l’exemple donné lors de la conférence :
// function types
#int(int, String)
On a là un type représentant une méthode prenant deux paramètres (un int et une String), et retournant un int.
Ceci est bien sûr un pré-requis pour un système de closures qui ne soit pas un simple sucre syntaxique…
Structures de contrôles NON
L’objectif des structures de contrôles consiste à redéfinir des méthodes utilisant des closures, mais qui puissent être appelées comme une structure du langage (if, for, while, etc.).
En fait le code de la closure serait passé de manière totalement transparente comme s’il s’agissait d’un bloc de code.
Concrètement cela permettrait de créer de nouvelle structure de contrôle plus évolué que celle existante. Les exemples les plus données étant une le withLock()
s’utilisant à la manière d’un bloc synchronized, ou un forEach()
travaillant directement sur le couple clef/valeur des Maps. Mais en fait cela offre une large gamme de possibilités…
Lock lock = ...
withLock(lock) {
System.out.println("hello");
}
Le bloc de code de cet exemple correspond en réalité à une closure, qui est passé implicitement comme second paramètre de la méthode withLock()
. L’intérêt étant d’obtenir un code bien plus clair.
On comprend également ici l’intérêt de la « liaison » entre les mots clefs return/break/continue
avec la méthode appelante : la closure est totalement invisible ici, et les mots clefs s’appliqueraient en toute logique au bloc parent…
On plonge ici dans toute la puissance des closures, nous permettant de définir autant de nouvelle structure de contrôle que nécessaire, sans être limité par le langage.
Toutefois, cela apporte énormément de complexité à la bonne compréhension des closures, et risque de troubler plus d’un développeur, car les closures disposerait alors de deux comportement différent. Or il s’agit d’une fonctionnalité qui n’est pas réellement destiné à tous les développeurs, mais uniquement à quelques concepteurs de librairies. En effet malgré les possibilités que cela pourrait offrir, la création de nouvelles structures de contrôles pourraient être assez limité.
Ceci devrait donc disparaitre de la proposition des closures pour Java 7…
Et donc ? Bonne nouvelle ou pas ?
La présentation de la conférence Devoxx était trop succincte. Difficile donc de se prononcer précisément pour le moment, car il reste de nombreuses zone d’ombre sur ce qui sera exactement couvert par les closures de Java 7. On a ici un échantillon des possibilités, et il ne reste plus qu’à voir ce que cela va donner.
Seule « certitude », les structures de contrôle et toutes les notions que cela implique ne devrait pas en faire partie, ce qui permet dans le même temps de fortement simplifier la compréhension des closures par les développeurs, et en soit ce n’est pas forcément un mal.
Toutefois, la porte ne reste pas totalement fermé, et ces mêmes structures de contrôle pourraient très bien revenir plus tard via une autre proposition (peut-être pour Java 8 une fois que les closures seront mieux appréhendées).
Et ce ne sera pas une mauvaise chose non plus…
8 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- Recuperation du nom des parametres
- Définition exacte de @Override
- [ fuite ] memoire
- Classes, méthodes private
- jre 1.5, tomcat 6.0 et multi processeurs
- Difference de performances Unix/Windows d'un programme?
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Possibilité d'accéder au type générique en runtime
Le fait de remplacer un type Java par un autre aboutit à une incompatibilité. Donc il en sera de même ici…
Donc, pour préserver la compatibilité, soit on continue d’utiliser l’interface, soit on surcharge
a++
Merci pour tes précisions.
Pour la classe anonyme affectée à un function type, j’imaginai le cas suivant :
Un développeur B utilise une classe anonyme dans son code pour implémenter une interface mono méthode d’une librairie A.
Si la librairie A est modifiée et l’interface en question remplacée par un function type et que le développeur B met à jour le jar de sa librairie mais ne modifie pas son code, que se passera t’il ? Compatible ou pas ?
La compatibilité avec les interfaces existantes permettra en effet d’utiliser les closures avec les API existantes.
Pour les nouvelles APIs, l’utilisation direct des closures aura l’avantage de ne pas multiplier les interfaces « inutilement ». Je crois que pendant la conférence il a été annoncé que le nouveau framework de Fork/Join nécessiterait 60 interfaces en cas d’absence des closures…
Je ne pense pas qu’on puisse affecter une classe anonyme à une function type… et je n’y vois pas trop d’intérêt.
Enfin coté performance il semble y avoir des avantages. Dans certains cas le code de la closures pourraient être inliné directement sans aboutir à une création d’objet… mais je n’ai aucune info précise sur le sujet…
a++
Si grosso modo, les « functions types » correspondent à des déclarations de types Java (Interface) et les « functions expression » à une implémentation concrète de ce type, puis je faire ces remarques :
La compatibilité avec les interfaces existantes était presque nécessaire sinon il aurait fallu déclarer d’abord des « functions types » préalables (dans son code) ou dans les API (réécriture).
Si ceci reste toujours possible, définir nos propres « functions types » qu’on implémentera par des « functions expressions », la compatibilité avec les interfaces existantes, c’est quand même réduit non car les interfaces mono méthode sont minoritaires.
Va t’on assister au développement d’API avec plusieurs interfaces mono méthodes ?
Entre des interfaces mono méthode pour être compatible avec les closures et l’utilisation de functions types, qu’est ce qui va déterminé le choix ? Si le type abstrait représenté est utilisé à un unique endroit, on déclare un function type mais s’il est utilisé à plusieurs endroits, l’interface mono méthode ne sera t’elle pas préférable ? puisque par essence plus réutilisable.
Pourra t’on également faire l’inverse : Déclarer un function type et utiliser une classe anonyme pour l’implémentation ? (même si ce cas n’aura probablement pas d’intérêt)
Et pour finir, hormis la rapidité de lecture/écriture pour le développeur, pour la machine virtuelle, ça change quelque chose ? Plus rapide, Kif Kif, ou plus lent ?
@lunatix : On devrait déjà pouvoir compter sur la compatibilité avec les interface mono-méthodes pour les APIs existantes.
En ce qui concerne l’API standard cela devrait principalement profiter au nouveau framework de Fork/Join… mais je ne pense pas que le reste de l’API puissent vraiment en profiter pour Java 7.
@Blaise1 : en gros c’est des classes anonymes, mais en beaucoup moins verbeux. Du coup cela peut favoriser leurs utilisations dans de nouvelles APIs. Bien souvent en utilisant les classes anonymes on se retrouve avec des pavés illisibles, si bien que l’intérêt est amoindri.
Par exemple comme le dit lunatix on n’a pas vraiment d’API permettant de filtrer les valeurs des collections, tout simplement car on fait tout aussi vite de faire sa boucle soit même :
Du coup il y a peu d’intérêt à de tels APIs…
A l’inverse la même API devient bien plus intéressante avec les closures :
Moi j’attends de voir comment les closures seront utilisées dans l’API existante. C’est a mon avis ce qui changera tout.
Si c’est bien intégré, ça peut simplifier considérablement les problèmes de Listeners, EDT, …
Par contre si se contente de permettre les closures sans mettre a jour l’API en conséquence, ça risque de n’être jamais utilisé.
Les closures..
LE sujet à la mode. Décidément je ne vois pas du tout ce que cela apporte d’autres que quelques lignes de codes en moins. Je n’y vois qu’un sucre qui permet en plus de compliquer le code avec des appels à des méthodes identifiées par des variables.
Il me faudrait peut-être un vrai exemple concret pour avoir l’illumination mais pour l’instant… rien.
Je vois quand même une semblant de « multi catch » qui aurait vraiment apporté un plus alors bon, si cette bidouille permet d’apporter un plus via le « multi catch » pourquoi pas. Mais le « multi catch » tout seul aurait été suffisant et aurait fait couler moins d’encre.
merci pour ton billet,c ‘est tres interessant.
Ce que je me demande, c’est ce qu’ils vont faire des librairies standard du coup. Difficile d’imaginer une api collections sans methodes filter et transform alors que les closures sont la. (pareil pour swing)