novembre
2011
Comme pour le JDK7, les premiers builds en « Developer Preview » du JDK8 sont disponible depuis quelques temps à l’adresse habituelle : http://jdk8.java.net/download.html
Mais pour l’instant, ces builds s’avèrent peu intéressant pour le commun des développeurs, du fait qu’ils se contentent principalement d’intégrer des correctifs sans « grosses » nouvelles fonctions ou APIs. Mais le projet Lambda a publié une autre « Developer Preview » plus intéressante : elle ne prend pas le dernier build du JDK8 mais elle y intègre le support des expressions lambda et des fonctionnalités qui y sont apparentées : http://jdk8.java.net/lambda/
En clair, c’est ce qu’il vous faut si vous voulez jouer avec les expressions Lambda…
Les expressions Lambda
Les expressions Lambda correspondent à une nouvelle syntaxe permettant d’écrire un « morceau de code ».
Elle se décompose en deux partie séparés par une flèche simple ( ->
).
A gauche on retrouve la liste des paramètres entre parenthèses. A droite un bloc de code entre accolades.
Exemple :
(Type arg1, Type arg2) -> { instruction1; instruction2; return value; }
Ce type d’expression va remplacer les classes anonymes pour l’implémentation des interfaces mono-méthode.
En effet les expressions Lambda doivent être affectées à une interface SAM (Single-Abstract-Method).
Prenons l’exemple de l’implémentation d’un Comparator
//
// Code Java 7 et inférieur utilisant une classe anonyme :
//
Comparator<String> ignoreCase = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
//
// Code Java 8 utilisant une expression Lambda :
//
Comparator<String> ignoreCase = (String s1, String s2) -> { return s1.compareToIgnoreCase(s2); };
Les expressions Lambda peuvent encore être simplifiées lorsqu’elles ne comportent qu’une seule et unique instruction : les accolades et le mot-clef return deviennent alors optionnels :
//
// Code Java 8 utilisant une expression Lambda :
//
Comparator<String> ignoreCase = (String s1, String s2) -> s1.compareToIgnoreCase(s2);
De plus, on peut également se passer du type des paramètres grâce au « Type Inference » qui le déduira automatiquement du le type des paramètres de l’interface associée :
//
// Code Java 8 utilisant une expression Lambda et le Type Inference :
//
Comparator<String> ignoreCase = (s1, s2) -> s1.compareToIgnoreCase(s2);
Bien entendu, dans tous les cas l’instance ainsi créé s’utilise de manière tout à fait normale, et le code s’exécute en utilisant la méthode décrite dans l’interface en question quel que soit le moyen qu’on a utilisé pour créer l’instance :
int result = ignoreCase.compare("hello", "HELLO");
Les références de méthode
Les références de méthode (Method references) permettent de référencer une méthode quelconque (en respectant les règles de visibilité).
La syntaxe est également composé de deux parties séparées par le caractères dièse ( #
) – Note : la syntaxe n’est pas définitive et il est possible que ceci soit remplacé par deux double-points dans les prochains builds ( ::
).
La partie de gauche doit correspondre soit au nom du type Java dans lequel on va rechercher la méthode, soit à une instance de l’objet en question.
La partie de droite doit correspondre au nom de la méthode, éventuellement suivi par le type de ses paramètres.
Prenons en exemple la méthode compareToIgnoreCase(String) de la classe String, définie comme ceci :
public int compareToIgnoreCase(String str)
Il est possible de la référencer via l’instruction suivante : String#compareToIgnoreCase(String) qui correspond à une signature int(String,String). En effet puisqu’il s’agit d’une méthode d’instance, un paramètre du type de l’objet est rajouté au début de la méthode, afin de pouvoir lui associer une instance.
Tout comme les expressions Lambda, les références de méthode peuvent être affectées à un objet correspondant à une interface SAM.
Pour rester sur le même exemple :
//
// Code Java 8 utilisant une référence de méthode
//
Comparator<String> ignoreCase = String#compareToIgnoreCase(String);
A noter que l’on peut également utiliser une référence de méthode static ou via une instance spécifique à la place du type.
Dans ce cas les paramètres ne seront pas modifié. De même, on peut référencer un constructeur en utilisant le mot-clef new.
Les méthodes d’extension virtuelles
Les méthodes d’extension virtuelles (Virtual Extension Methods), anciennement nommées « Defender Methods », viennent combler une des grosses lacunes des interface de Java : l’évolution !
Ou plutôt l’absence d’évolution. En effet à partir du moment où une interface publique fait partie d’une API, elle ne peut plus être modifiée sous peine de provoquer des erreurs de compilations ou de générer des exceptions à l’exécution. L’API standard de Java a longtemps souffert de cela. Des interfaces standards largement utilisées comme Collection, List ou Map ne pouvant pas être modifié sans casser tout l’écosystème Java !
Les VEMs permettent donc aux concepteurs d’API de faire évoluer leurs interfaces en limitant les incompatibilités. Il est donc désormais possible de rajouter des méthodes dans une interface à condition d’indiquer une méthode static représentant l’implémentation par défaut qui sera utilisée.
Si la classe n’implémente pas la nouvelle méthode, ce sera la méthode static par défaut qui sera exécutée.
Cela permet d’étendre les interfaces tout en conservant les possibilités de surcharge puisque les classes sont libre d’implémenter (ou pas) ces méthodes d’extension virtuelles…
ATTENTION : les VEMs ne sont pas complètement implémentées dans cette « developer preview ». Si la compilation de l’interface et de la classe ne pose aucun problème, son exécution génèrera une AbstractMethodError. Pour le moment cela neccessite toujours une étape supplémentaire de « pre-link » manuel…
Les fonctions
Ces nouvelles fonctionnalités font la part belle aux interfaces SAM.
L’API standard intègrera donc un nouveau package java.util.functions qui regroupera une série d’interface de base qui pourront être utilisé avec les expressions Lambda ou les références de méthode.
Block
public interface Block<T> {
public void apply(T t);
}
Mapper
public interface Mapper<T, U> {
public U map(T t);
}
Operator
public interface Operator<T> {
public T eval(T left, T right);
}
Predicate
public interface Predicate<T> {
public boolean eval(T t);
}
Ces fonctions bénéficient également de méthodes d’extension virtuelle permettant de simplifier certains traitements.
Exemples
Toutes ces fonctionnalités ont un gros avantage : elles pourront être utilisées avec les APIs existantes.
En effet l’API standard regorge déjà d’interface SAM, même si le concept n’était pas ainsi nommé.
Trier une List
List<String> list = ...
//
// Code Java 7 et inférieur :
//
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
});
//
// Code Java 8 avec une expression Lambda :
//
Collections.sort(list, (String a, String b) -> a.compareToIgnoreCase(b) );
//
// Code Java 8 avec expression Lambda et Type inference :
//
Collections.sort(list, (a, b) -> a.compareToIgnoreCase(b));
//
// Code Java 8 avec une Method Reference :
// (syntaxe non-définitive)
//
Collections.sort(list, String#compareToIgnoreCase);
Mais ce n’est pas tout : on peut aller encore plus loin grâce aux VEPs.
En effet l’interface List a été enrichie via une nouvelle méthode sort() (entre-autres) définie la manière suivante :
public void sort(Comparator<? super E> c)
default Collections.<E>sort;
On peut donc désormais utiliser la méthode sort() directement :
//
// Code Java 8 avec une Method Reference via les VEPs
//
list.sort(String#compareToIgnoreCase);
Ce code est d’ailleurs plus avantageux. En effet cet appel n’utilisera pas forcément la méthode par défaut.
C’est la JVM qui l’exécutera si et seulement si l’instance courante ne l’a pas implémentée. Ce qui veut dire que chaque type pourrait potentiellement proposer une implémentation plus performante si cela a lieu d’être.
Dans ce cas précis, on pourrait imaginer que les différentes implémentations de List proposent une méthode sort() en adéquation avec leurs structures de données.
Gestion des listeners Swing
De nombreux listeners Swing se contentent d’appeler une méthode de la classe courante.
Autant utiliser directement une référence de méthode !
//
// Code Java 7 et inférieur :
//
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
uneMethodAExecuterLorsDuClic(e);
}
});
//
// Code Java 8 avec une Method Reference :
//
button.addActionListener(this#uneMethodAExecuterLorsDuClic);
De plus la méthode ainsi lié peut tout à fait être une méthode private
Création de Thread
Les Threads attentent une instance de Runnable en paramètre, que l’on peut créer via les expressions lambda ou les références de méthodes :
//
// Code Java 7 et inférieur :
//
new Thread(new Runnable() {
@Override
public void run() {
// CODE
}
}).start();
//
// Code Java 8 avec une expression Lambda :
//
new Thread(()-> {
// CODE
}).start();
//
// Code Java 8 avec une référence de méthode :
//
new Thread( this#uneMethode ).start();
On peut bien sûr faire la même chose avec beaucoup d’autres classes
Des Factory simplifiés
Les références de méthode permettent également de référencer les constructeurs en utilisant le mot-clef new à la place du nom de la méthode.
On peut donc utiliser cela pour créer les factorys les plus basiques :
//
// Code Java 7 et inférieur :
//
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
};
//
// Code Java 8 avec une expression Lambda :
//
ThreadFactory factory = Thread#new(Runnable);
On pourrait dénombrer encore de multiples exemples d’utilisation avec l’API standard actuelle, mais le plus intéressant viendra des nouvelles API qui exploiteront pleinement ces nouvelles possibilités.
L’API de Collections devraient être la première à en profiter
7 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Classes, méthodes private
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Définition exacte de @Override
- jre 1.5, tomcat 6.0 et multi processeurs
- Possibilité d'accéder au type générique en runtime
- Recuperation du nom des parametres
- [ fuite ] memoire
- Difference de performances Unix/Windows d'un programme?
[…] ? Et puis les Lambda, ce ne sont pas les articles qui manquent sur les blogs comme Developpez.com (ici ou ici) ou les talks dans les JUG. Bref, je n’étais pas un terrain conquis dès le départ, […]
@Alex : en effet merci j’ai corrigé cela
a++
Une petite coquille dans la partie « Les références de méthode » :
versus
Les évolutions de C# et Java peuvent se ressembler, elles peuvent également adopter une approche assez différente pour des fonctionnalités différentes au final.
Je n’ai pas assez d’expérience en C# en ce qui concerne les Lambdas.
Mais par exemple au niveau des méthodes d’extensions les approches sont relativement opposées et ne permettent pas les mêmes fonctionnalités (avec chacune leurs qualités et leurs défauts) (plus de détail dans ce billet « Extension Methods » de C# 3.0 VS « Defender Methods »)
a++
> Mais il y a quand même pas mal de différence derrière
Je m’en doute bien, si c’était pareil, ça se saurait
Après, si ça s’écrit pareil, et que ça donne le même résultat, le fait que ce soit implémenté différemment est un détail d’implémentation
La syntaxe des expressions Lambda est volontairement similaire à celle de C#
Mais il y a quand même pas mal de différence derrière !
a++
Ca me rappelle qq chose, tout ca
ca tombe bien, je vais pouvoir adapter mes cours sur les lambdas pour mes collègues java-ciens