
En fin d'année dernière, le report de Java 7 laissait envisager l'intégration des closures. Cela a donné naissance au projet Lambda dont l'objectif était de regrouper les différents travaux afin d'en sortir une spécification claire et fonctionnelle quitte à se passer de certain "power-concept".
Il en ressort une proposition d'expressions Lambda relativement allégée vis à vis des multiples et très complètes propositions de closures qui ont pu être proposées par le passé. Mais cela s'accompagne également de nouveaux concepts fort intéressants (Exception Transparency, Method References, Defender Methods).
Voyons voir de quoi il en retourne...
Attention : je reporte ici des informations de la liste de diffusion du groupe de travail du projet Lambda. Les fonctionnalités précises ainsi que la syntaxe ne sont pas définitives et peuvent encore évoluer...
Il ne s'agit pas vraiment d'un nouveau type ni d'une réelle nouveauté du langage, mais plus précisément d'un nouveau concept. Un type SAM correspond tout simplement à un type Java (classe ou interface) possédant une seule et unique méthode abstraite.
On peut prendre comme exemple l'interface Runnable, qui ne définit qu'une seule et unique méthode run().
A première vue cela peut paraitre totalement insignifiant, mais cela revêt en fait une importance considérable.
Les expressions lambda représentent un "morceau de code". On pourrait considérer ceci comme une "méthode anonyme", ou comme une manière plus concise d'écrire une classe anonyme mono-méthode.
Son principal intérêt étant de proposer une syntaxe réduite au stricte minimum :
Par exemple, l'expression lambda suivante permet de comparer deux variables de type String en ignorant la case :
#(String a, String b) { return a.compareToIgnoreCase(b); }
C'est là que rentrent en jeu les types SAM. En effet les expressions lambda peuvent être automatiquement converties vers un type SAM dont la signature de la méthode abstraite correspond à celle de l'expression lambda.
Dans notre cas on peut affecter cela à un Comparator :
Comparator<String> comparator = #(String a, String b) { return a.compareToIgnoreCase(b); };
Actuellement pour obtenir le même résultat, il faut utiliser une classe anonyme bien plus verbeuse :
Comparator<String> comparator = new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareToIgnoreCase(b); } };
Et encore la syntaxe peut encore être allégée. Il est en effet possible de ne pas spécifier le type des paramètres (ils seront déduits en fonction du type SAM correspondant). De même si le code se limite à une seule instruction, on peut se passer du return et du point-virgule. Ce qui nous donne alors ceci :
Comparator<String> comparator = #(a, b) { a.compareToIgnoreCase(b) };
Les types SAM étant assez nombreux dans l'API, ceci permettra d'utiliser massivement les expressions lambda y compris avec les APIs existantes qui n'avaient pas été conçus pour cela. Par exemple :
List<String> list = ... Collections.sort(list, #(a, b){a.compareToIgnoreCase(b)});
Peut être la nouveauté la moins visible pour la majorité des développeurs. Ce n'est pas vraiment un concept spécifique pour les expressions lambda, mais plutôt une évolution des Generics afin de permettre de mieux gérer les exceptions.
On peut bien se rendre compte du problème en regardant de plus près les interfaces Runnable et Callable, qui correspondent grosso-modo à ceci :
public interface Runnable { public void run(); } public interface Callable<V> { public V call() throws Exception; }
Si on omet le type de retour, la seule différence vient de la gestion des exceptions.
Actuellement il est tout à fait possible d'utiliser les Generics pour paramétrer cela :
public interface XCallable<V, E extends Exception> { public V call() throws E; }
Mais cela conserve une grosse limitation dû au caractère unique de chaque paramétrage.
En fait si l'on utilise un seul type d'exception cela fonctionne parfaitement :
XCallable<String, IOException> callable = ... try { callable.call(); } catch (IOException e) { ... }
Mais il est impossible de spécifier plusieurs types d'exceptions différents (IOException et SQLException par exemple), à moins de se résigner à continuer d'utiliser le type Exception bien trop générique...
La notion d'exception transparency consiste à faire évoluer les Generics afin de gérer plus spécifiquement le cas des exceptions. Pour cela le paramétrage des exceptions se distinguera par le mot-clef throws. Notre interface deviendrait alors :
public interface XCallable<V, throws E> { public V call() throws E; }
A première vue pas de changement majeur. La différence survient principalement lors de l'utilisation, car on peut alors spécifier plusieurs types d'exceptions :
XCallable<String, IOException|SQLException> callable = ... try { callable.call(); } catch (IOException e) { ... } catch (SQLException e) { ... }
A noter qu'il sera également possible de préciser qu'aucune exception ne sera remontée, ce qui permet de s'éviter un try/catch inutile :
XCallable<String, void> callable = ... callable.call(); // pas d'exception
Bref cela permettra de paramétrer finement les exceptions d'une méthode.
Comme le titre l'indique si bien, les Method References permettront de référencer une méthode de manière sécurisée (vérifiée à la compilation).
La syntaxe devrait correspondre à celle utilisée dans la javadoc pour référencer une méthode, c'est à dire le nom de la classe séparée du nom de la méthode par le caractère #.
Quelques exemples :
System#getProperties() String#compareToIgnoreCase(String)
Tout comme les expressions Lambda, les Method References peuvent être affectées à un type SAM correspondant. A noter que pour une méthode d'instance on obtiendra un paramètre initial supplémentaire qui devra contenir une instance du type de la classe.
Callable<Properties> properties = System#getProperties(); Comparator<String> compare = String#compareToIgnoreCase(String);
A noter qu'il devrait être possible d'appeler directement une méthode d'instance sur une instance précise en utilisant une variable précise en lieu et place du nom de la classe :
String text = "Hello World !"; Callable<String> upperCase = text#toUpperCase(); System.out.println( upperCase.call() ); // Affiche : HELLO WORLD !
Cela permettra de "passer" une méthode en paramètre d'autres méthodes. Pour reprendre l'exemple du sort() :
List<String> list = ... Collections.sort(list, String#compareToIgnoreCase(String));
A noter que les Method References devraient également pouvoir être converties vers des objets MethodHandles de la JSR292 (dynamic language).
J'en ai déjà parlé plus longuement il y a quelques temps, la dernière nouveauté poussée par le projet Lambda vise à permettre l'évolution des interfaces.
En effet tout ajout de méthode dans une interface de l'API est impossible sous peine d'engendrer une multitude d'incompatibilités dans toutes les classes qui l'implémentent, justement car elles n'implémenteront pas ces nouvelles méthodes.
Le rôle des Defender Methods consiste à permettre cela, en proposant une implémentation par défaut en cas d'absence d'implémentation
L'idée consiste donc à ajouter des méthodes d'extension qui préciseraient l'implémentation par défaut via une méthode statique dont les paramètres correspondent :
public interface List<E> { ... extension void sort() default Collections.sort; extension void sort(Comparator<? super E> c) default Collections.<E>sort; }
S'en suit alors deux traitements spécifiques lorsque ces méthodes ne sont pas implémentées:
Dès lors il devient possible de faire évoluer les interfaces "sans tout casser", en proposant une implémentation par défaut mais en conservant la possibilité de redéfinir une implémentation spécifique dans les classes filles si besoin.
http://blog.developpez.com/htsrv/trackback.php?tb_id=9232
Comparator comparator = #(String a, String b) { return a.compareToIgnoreCase(b); };
on pouvait faire:#int(String,String) comparator = #(String a, String b)#System.getProperties() //au lieu de System#getProperties()#composant.getText().length() //au lieu de composant.getText()#length() ça peux perturber, mais quand on est assez tordu pour faire ça, faut assumer.* le report de Java 7 laissait envisager
* quitte à se passer de certains "power-concept".
* Il en ressort une proposition d'expressions Lambda relativement allégée vis à vis des multiples et très complètes propositions de closures qui ont pu être proposées par le passé.
* ne sont pas définitives et peuvent encore évoluer...
* C'est là que rentrent en jeu les types SAM.
* Et encore la syntaxe peut encore être allégée.
* ils seront déduits en fonction du type SAM correspondant
* les APIs existantes qui n'avaient pas été conçues pour cela.
* de manière sécurisée (vérifiée à la compilation).
* La syntaxe devrait correspondre à celle utilisée dans la javadoc pour référencer une méthode, c'est à dire le nom de la classe séparée du nom de la méthode par le caractère #.
* les Method References devraient également
* des méthodes d'extension qui préciseraient l'implémentation
* les méthodes d'extension rajoutées malgré leurs absences
* avec un compilateur plus anciens
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