
Après BGGA et CICE, voici une présentation de la proposition de closures FCM qui se place exactement entre les deux précédentes : à la fois plus simple que BGGA mais également plus complète que CICE.
FCM se présente comme un compromis entre les deux, en proposant une approche plus simple sans pour autant trop perdre de possibilités.
De plus, tout comme "BGGA", la proposition "FCM" propose également un prototype sur lequel je me suis basé dans cet article (FCM-2008-02-25.zip), mais malheureusement il n'implémente pas toutes les fonctionnalités de la proposition...
Avant de commencer, je tiens à rappeler qu'il ne s'agit pour le moment que d'une proposition, ce qui fait que ce qui est décrit dans ce billet pourrait évoluer d'ici que les closures soit ajoutés au langage (si c'est le cas un jour). Ce billet a surtout pour objectif de présenter les possibilités que cela pourrait apporter au langage. De même, si ce n'est pas déjà fait, la lecture préalable du billet concernant la proposition BGGA est fortement recommandé car il présente les notions de manière plus approfondi !
Bonne lecture !
Sommaire / Accès rapide :
Pour être précis, la proposition FCM utilise le terme "anonymous inner method", qui correspond à une écriture simplifié des classes anonymes.
Par exemple, pour reprendre l'exemple d'une closure qui effectuerait la somme de deux valeurs entières, on obtiendrait le code suivant :
#(int x, int y) { return x + y; }
Le # est spécifique à la proposition CICE, On retrouve ensuite la liste des paramètre de la méthode entre parenthèses, suivi d'un bloc entre accolades permettant de délimiter le code de la manière la plus standard qui soit. On remarquera également, et contrairement à la proposition BGGA, la présence du mot-clé return qui s'applique bien ici au code de la méthode de la closure, et non pas à la méthode appelante.
Le choix du caractère # s'est fait pour deux principales raisons :
Les "Method Types" ne sont ni plus ni moins que l'équivalent des "Function Types" de BGGA, et permettent de déclarer dynamiquement un type représentant une seule et unique méthode :
#(int(int, int)) plus;
Ici, on déclare une référence nommée "plus", qui représente une "function type" prenant deux paramètres de type int, et retournant un int.
Les types sont déclarés dans le même ordre que dans la signature d'une méthode : le premier int représente la valeur de retour, et ceux qui suivent entre parenthèses correspondent aux paramètres. Le tout englobé dans une paire de parenthèse, dont l'objectif est principalement d'éviter les ambiguitées avec les autres syntaxes de la proposition (voir plus loin).
Le tout correspondrait donc à la définition d'une interface de la forme suivante :
public interface III { public int invoke(int x, int y); };
Au final la déclaration et l'initialisation d'une closure FCM pourrait ressembler à ceci :
#(int(int, int)) plus = #(int x, int y) { return x + y; };
On est alors libre de manipuler la référence "plus" comme n'importe quelle référence. On peut donc la passer à d'autre méthode et appeler sa méthode invoke() autant de fois que neccessaire...
Les "Method Types" et les closures peuvent bien sûr prendre plusieurs forme, et remonter des exceptions, par exemple :
// Définition d'une "function type" prenant un double en paramètre, // et retournant une String // Implémentation d'une "closure" qui formate le double en String #(String(double)) format = #(double d) { return String.format("%.2f", d); }; // Définition d'une "function type" prenant un int en paramètre, // et pouvant remonter une exception // Implémentation d'une "closure" qui fait une pause de 'n' secondes #(void(int) throws Exception) sleep = #(int n) { Thread.sleep(n*1000); }; // Définition d'une "function type" pouvant remonter une exception // Implémentation d'une "closure" remontant une exception dans tous les cas #(void() throws IOException) boom = #() { throw new IOException("Boom") }; // Définition d'une "function type" sans paramètre ni type de retour // Implémentation d'une "closure" qui affiche un "Hello World !" #(void()) hello = #() { System.out.println("Hello World !"); }; // Définition d'une "function type" qui retourne une valeur entière // Implémentation d'une "closure" retourne un nombre entre 0 et 100 #(int()) number = #() { return (int)(Math.random()*100.0); };
Note : lorsque la closure ne comporte aucun paramètre, il est possible de se passer des parenthèses vides dans la définition de "anonymous inner class", ce qui donnerait dans le dernier exemple ci-dessus :
#(int()) number = #{ return (int)(Math.random()*100.0); };
La spécification FCM ne met pas vraiment en avant les "method types", et les présente même comme optionnel (en leurs priviliégiant l'utilisation d'interface - voir plus loin la section Closures Conversions). De plus cela n'étant pas implémenté dans le prototype il n'est pas possible de savoir s'il y a un équivalent aux notions de covariance et de contravariance présentent dans BGGA.
FCM permet également l'accès total aux variables locales à l'intérieur des closures et inner-classes, sans utilisation de mot-clé particulier. Si cela devrait se faire dans les mêmes conditions que BGGA (sans l'annotation @shared), on reste encore une fois dans le flou puisque la spécification est relativement succincte et que le prototype n'implémente pas cela.
Toutefois il devrait logiquement être possible d'utiliser un code comme celui-ci :
int count = 0; #(void()) print = #{ System.out.println( count++ ); }; print.invoke(); // Affiche : 0 print.invoke(); // Affiche : 1 print.invoke(); // Affiche : 2 print.invoke(); // Affiche : 3 System.out.println("count = " + count); // Affiche : count = 4
La seule particularité de FCM vient d'une annotation @ConcurrentLocalVariableAccess à utiliser sur les paramètres des méthodes afin d'indiquer que le code de la closure sera exécuté dans un thread séparé, et nécessitera donc un synchronisation... mais son utilisation précise reste confuse.
Tout comme BGGA, FCM met en place la conversion de closure afin de les rendre compatible avec les interfaces "mono-méthode" et les classes "mono-méthode-abstraite"
De ce fait, si on reprend notre premier exemple qui se contentait de sommer deux int :
#(int(int, int)) plus = #(int x, int y) { return x + y; };
On pourrait très bien utiliser une interface standard à la place de la "method types" :
public interface IntPlus { int add(int x, int y); }
Ce qui nous donnerait :
IntPlus plus = #(int x, int y) { return x + y; };
La closure est alors automatiquement convertie en une implémentation de l'interface, et le code de la closure sera alors accessible via la méthode add() défini par l'interface.
FCM met vraiment cela au premier plan, bien plus que les "method types" qui sont presque optionnel (Stephen Colebourne, l'un des auteurs de la proposition, envisage parfaitement l'implémentation de FCM sans les "method types").
En fait la proposition FCM prévoie surtout de remplacer les classes anonymes par des "inner method", mais déconseille l'utilisation des "method types" au profit d'une interface ou classe clairement définit. Ceci bien sûr afin de faciliter la lecture du code.
De plus, la proposition FCM se distingue également par la notion de "named inner methods" qui permet d'utiliser la syntaxe des closures pour hériter d'une classe concrête, en précisant l'unique méthode à redéfinir. Il suffit pour cela re spécifier le nom de cette méthode juste avant la déclaration des paramètres :
// La classe WindowAdapter défini 10 méthodes concrêtes, // mais on peut quand même utiliser les closures FCM pour redéfinir une seule et unique méthode : WindowAdapter adapter = #windowClosed(MouseEvent e) { /* ... */ };
Ce qui serait grosso-modo l'équivalent du code suivant :
WindowAdapter adapter = new WindowAdapter() { @Overrides public void windowClosed(MouseEvent e) { /* ... */ } };
Bien sûr si on veut hériter d'une ou plusieurs méthodes, on devra toujours passé par des classes anonymes. Mais ce n'est pas le but des closures ![]()
Ceux qui ont bien suivi la présentation sur BGGA auront noté que la notion de "method references" vient de FCM. Il est donc logique qu'on la retrouve ici sous la même forme :
Nom # identifier ( TypeList )
Où :
Nom correspond soit à un nom de classe, soit à une référence valide.identifier correspond au nom de la méthode.TypeList correspond à la liste des paramètres de la méthode.Pour rappel cette syntaxe permet d'utiliser une méthode existante comme une closure.
Il est donc possible d'associer une méthode à une "method type" :
#(void(Component, Object)) showMessage = JOptionPane#showMessageDialog(Component, Object);
Ou plus globalement directement à une interface compatible :
Runnable doNothing = Thread#yield();
La proposition distingue clairement les trois types de références, tel qu'il ont été repris par BGGA :
Static method references, qui permet donc de transformer une méthode statique :
#(double()) ref = Math#random();
On conserve logiquement la même signature de part et d'autre.
Instance method references, qui permet d'associer une méthode d'instance. La "method type" (ou l'interface) associée devra alors posséder un paramètre supplémentaire qui correspondra à l'instance sur laquelle on souhaite appeler la méthode :
#(int(List, Object)) ref = List#indexOf(Object);
Bound method references, qui permet d'associer une méthode d'instance avec une référence existante (on obtient de nouveau une signature similaire, et tous les appels se feront sur l'instance référencée) :
List<String> myList = ...; #(int(Object)) ref = myList#indexOf(Object);
A noter également l'existence de la notation #method() qui est l'équivalent de this#method(), et qui permet donc de référencer une méthode de la classe courante.
FCM et défini également la notion de Constructor references, qui permet de référencer un constructeur de la même manière. La syntaxe est identique si ce n'est que le nom de la méthode est omis :
#(Integer(int)) ref = Integer#(int);
Ce qui pourrait s'avérer fort pratique pour implémenter rapidement et simplement des "fabriques" :
ThreadFactory factory = Thread#(Runnable);
Mais la proposition FCM va beaucoup plus loin en apportant la notion de "Member literals"...
La problématique est un peu éloigné des closures, mais assez intéressante puisqu'elle consiste à améliorer l'utilisation de la reflection. En effet, l'API de réflection de Java est très puissante et permet beaucoup de chose, mais possède un très gros problème : toutes les vérifications (ou presque) sont effectué à l'exécution. En effet tous les noms de méthodes, constructeurs ou d'attributs sont généralement passé en utilisant leurs noms via une String, ce qui fait que le compilateur n'a aucun moyen de vérifier cela. Toutes les vérifications s'effectueront donc à l'exécution, ainsi que les éventuelles erreurs...
L'objectif des "Member literals" est de proposer une alternative sécurisé aux méthodes getMethod(), getConstructor() et getField(), tout comme nous avons déjà une version sécurisé de Class.forName("Name") via le litéral Name.class (le compilateur peut vérifier l'existence de la classe et certaines contraintes).
Les "Member literals" utilisent exactement la même syntaxe que les "Method references", et permettent de référencer les différents composants d'une classe (constructeurs, méthodes et attributs) d'une manière sécurisé (dans le sens où le compilateur pourra vérifier leurs existences dès la phase de compilation).
Les Method literals permettent donc de référencer une méthode afin d'obtenir l'instance de java.lang.reflect.Method correspondant :
// Que ce soit pour une méthode static : Method min = Math#min(int, int); Method sort = Collections#sort(List, Comparator); // Ou une méthode d'instance : Method shortValue = Integer#shortValue(); Method equals = Object#equals(Object);
Ce qui est l'équivalent du code suivant :
// Que ce soit pour une méthode static : Method min = Math.class.getMethod("min", int.class, int.class); Method sort = Collections.class.getMethod("sort", List.class, Comparator.class); // Ou une méthode d'instance : Method shortValue = Integer.class.getMethod("shortValue"); Method equals = Object.class.getMethod("equals", Object.class);
Les Constructor literals permettent de référencer un constructeur de manière précise, et sont donc assignable vers le type java.lang.reflect.Constructor<T> :
Constructor<ArrayList> newList = ArrayList#(int); Constructor<JButton> newList = JButton#(String, Icon);
Ce qui reviendrait à écrire en utilisant l'API de réflection :
Constructor<ArrayList> newList = ArrayList.class.getConstructor(int.class); Constructor<JButton> newList = JButton.class.getConstructor(String.class, Icon.class);
Enfin, les Field literals permettent quand à eux d'obtenir une instance de java.lang.reflect.Field représentant un attribut:
// Que ce soit pour un attribut static : Field out = System#out; Field exitValue = JFrame#EXIT_ON_CLOSE; // Ou bien un attribut d'instance : Field width = Dimension#width;
De même, cela se fait actuellement via le code suivant :
// Que ce soit pour un attribut static : Field out = System.class.getField("out"); Field exitValue = JFrame.class.getField("EXIT_ON_CLOSE"); // Ou bien un attribut d'instance : Field width = Dimension.class.getField("width");
Tout ceci permettant de sécurisé un peu plus l'utilisation de la reflection, en permettant au compilateur de vérifier la cohérence des litéraux, et donc en évitant de reporter ces erreurs lors de l'exécution. Le tout bien sûr en respectant les règles de visibilités standards de ces éléments selon le contexte...
Contrairement à BGGA, la proposition FCM ne permet pas de définir de nouvelle structure de contrôle et se limite aux closures en elle même.
Toutefois, à l'instar de CICE et ARM, la proposition FCM est associé à la proposition JCA qui propose l'ajout d'une nouvelle syntaxe permettant de redéfinir des structures de contrôles.
Reprenons comme exemple un éventuelle bloc synchronizedWith qui fonctionnerait de la même manière que le mot-clef synchronized, mais en utilisant un Lock de l'API de concurrence de Java 5.0, et dont la structure s'utiliserait de la manière suivante :
Lock lock = new ReentrantLock(); synchronizedWith(lock) { System.out.println("Synchronized !"); }
Pour cela, il faut déclarer une méthode synchronizedWith() avec une syntaxe un peut particulière du fait que ses paramètres seront séparé en deux parties :
public static void synchronizedWith(#(void()) block : Lock lock) { lock.lock(); try { block.invoke(); } finally { lock.unlock(); } }
La méthode prend deux types de paramètre :
Lock en l'occurrence.Ces deux types de paramètres sont séparés par le symbole " : " afin de bien les distinguer (à la manière du for étendu de Java 5.0).
Contrairement à BGGA, la proposition JCA simplifie au maximum son utilisation, et évite tout les cas particulier :
Il est également possible d'utiliser des structures de contrôles dont le bloc de code attend des paramètres.
Pour cela nous allons reprendre l'exemple d'une structure with qui permettrait d'utiliser un objet "closeable" en le fermant proprement à la fin du traitement, et qui s'utiliserait de la manière suivante :
with(FileInputStream in : new FileInputStream("x")) { /* code */; }
Cela nous donnerait donc une méthode/structure de contrôle de la forme suivante :
public static <C extends Closeable> void with( #(void(C)) block : C flux ) { // Le flux est créé juste avant l'appel et reçu en paramètre try { // On exécute le code de la closure associée block.invoke(flux); } finally { // Puis on ferme le flux dans tous les cas flux.close(); // throws IOException } }
On remarquera principalement que le choix de la syntaxe permet de conserver le même ordre des paramètres dans la définition de la méthode que dans l'utilisation de la structure de contrôle.
Le bloc de code pourra accepter le code de n'importe quel bloc, et interagir avec la méthode parente (return, throw exception, break/continue par rapport à une boucle parente...)
Il est également possible de définir des structures de contrôle de boucle permettant de reproduire le comportement du for étendus de Java 5.0. Il suffit pour cela de préfixer le premier paramètre par le mot-clef for :
public static <T> void each(for #(void(T)) block : Iterable<T> iterable) { Iterator<T> iterator = iterable.iterator(); while (iterator.hasNext()) { block.invoke(iterator.next()); } }
L'appel se fait de manière similaire à la proposition BGGA, c'est à dire en utilisant une fois de plus le mot-clef for :
List<String> list = ... // La syntaxe d'appel est proche de celle du for étendu de Java 5.0 : for each(String str : list) { System.out.println(str); }
Désormais, les mot-clefs break et continue s'appliqueront à notre structure de contrôle et non pas à une boucle parente.
Il est bien sûr possible d'utiliser des bloc de code avec plusieurs paramètres, comme par exemple avec ce code permettant de parcourir les éléments d'une Map de manière distincte :
public static <K, V> void each(for #(void(K, V)) block : Map<K, V> map) { for (Map.Entry<K, V> entry : map.entrySet()) { block.invoke(entry.getKey(), entry.getValue()); } }
Et qui s'utiliserait de la manière suivante :
Map<String, Date> map = ... for each(String key, Date value : map) { System.out.println("key = " + key + ", value = " + value); }
J'ai déjà parlé de l'intérêt des structures de contrôle sur le billet de la proposition BGGA, et force est de constater que cette proposition JCA/FCM en conserve les grandes lignes sous une forme bien plus simple.
On remarquera toutefois, en comparaison avec BGGA, les limitations suivantes :
Au final la proposition FCM, couplée à JCA pour la gestion des structures de contrôle, se révèle relativement complète en proposant de manière plus "simple" et directe la quasi-totalité des fonctionnalités de BGGA, tout en limitant fortement sa complexité.
Je dois dire qu'entre BGGA et FCM/JCA mon coeur balance, et aurait un peu tendance à pencher vers ce dernier du fait de sa simplicité.
Malheureusement à l'heure actuelle il y a très peu d'information sur le sujet : on ne sait toujours pas si Java 7 intègrera ou non les closures, si ce sera remis à une version ultérieure, ou si on devra s'en passer définitivement...
http://blog.developpez.com/htsrv/trackback.php?tb_id=6348
Field width = Dimension#width; Vous devez être identifié pour poster un commentaire.

Ce blog est mis à disposition sous un contrat Creative Commons BY-NC-SA.
System.gc() a encore frappé : jre 1.5, tomcat 6.0 et multi processeurs
Tutoriels Java SE
Tutoriels Java EE
Sélection du blog
Java SE/EE et Web en général
| Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Copyright © 2000-2012 - www.developpez.com