Archive for April, 2012

JDK8 Lambdas in Action

Saturday, April 21st, 2012

Les lambdas sont une nouvelle fonctionnalité très attendue du JDK 8 (version finale prévue pour septembre 2013).

Cet article reprend une présentation faite à Devoxx Paris 2012 dans laquelle j’ai fait une démonstration de code utilisant les lambdas dans la preview du JDK8.

Ces exemples mettent aussi en évidence certains choix importants dans la facon dont les lambdas sont implémentés dans le JDK 8.

Tout d’abord les binaires du JDK 8 Preview sont disponibles (avec des release régulières suivant l’avancement d’implémentation) ici : http://openjdk.java.net/projects/lambda.

Une fois le JDK installé, on peut écrire un premier exemple affichant tous les éléments d’une liste de String :

public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add(”Guillaume”);
names.add(”Eric”);
names.add(”David”);

names.forEach(s->System.out.println(s));

}}

Les collections du JDK 8 proposent une méthode forEach(Block) qui peut prendre une expression Lambda en paramètre.

Cette expression lambda s->System.out.println(s) pourrait aussi s’écrire (String s)->{ System.out.println(s); }.

Une expression lambda plus complexe avec plusieurs paramètres en entrée, executant plusieurs instructions  et retournant une valeur s’écrira

(String s, Object o)->{ System.out.println(s); return s + o ;}

L’inférence de type permet d’omettre le type des paramètres dans les cas ou ce type peut être déduit du contexte :

(s)->{ System.out.println(s); }

Dans le cas ou l’on a une seule instruction dans le block de l’expression, on peut ommettre les accolades:

(s)->System.out.println(s)

Si l’expression a un seul paramètre en entrée, on peut supprimer les parenthèses :

s->System.out.println(s)

Et enfin on peut aussi utiliser une syntaxe pour faire directement un alias vers une méthode existante :

System.out::println

Cette syntaxe permet de créer des alias vers des méthodes statiques, des méthodes d’instance ou des constructeurs

De la même facon qu’on a utilisé la méthode forEach, on peut aussi utiliser la méthode filter(Predicate).

Les expressions lambdas passées en paramètre de forEach ou filter peuvent être extraite sous forme de variable locales :

Predicate<String> longName= s->s.length()>4;

Block<String> display = s->System.out.println(s);

names.filter(longName).forEach(display);

Les interfaces Predicates et Block sont des “Functional Interfaces” : interfaces comportant une seule méthode abstraite.

On peut affecter une expression lambda à toute Functional Interface dont l’unique méthode a une signature qui correspond à celle de l’expression lambda.

Ceci permet d’utiliser des expressions lambdas avec de nombreuses librairies existantes, sans aucune modification de ces librairies : on peut par exemple utiliser une expression lambda dans tout API utilisant un Runnable ou Callable, ou un ActionListener.

Si l’on regarde plus en détail le contenu des interfaces Predicate ou Block, on voit dans l’interface Predicate qu’en plus de la méthode

boolean eval(T t);

On a d’autres méthodes non abstraites : ces méthodes on une implémentation précédée du mot clef default!

Predicate<T> and(Predicate<? super T> p) default {

return Predicates.and(this, p);

}

Le jDK 8 introduit cette notion appelée Virtual Extension Method, qui permet d’ajouter dans une interface des méthodes non abstraites avec une implémentation par defaut.

Les classes qui implémentent l’interface bénéficient automatiquement de ces définition, et peuvent éventuellement redéfinir cette implémentation.

Cette notion de VEM est introduite en partie à cause de l’introduction des lambdas.

En effet, pour bénéficier complétemet des lambdas il est nécessaire de faire évoluer les librairies de collections afin d’y ajouter des méthodes très attendues : filter(), map(), reduce(), etc.

Si on ajoute ces méthodes aux interfaces Collection ou List, cela casse toute implémentation de ces interface qui aurait été écrite avant le JDK8, ce qui n’est pas envisageable avec la base de code java existante aujourd’hui.

Il n’est pas non plus envisageable de créer de nouvelles interfaces List2, Collection2, uniquement pour éviter de casser la compatibilité avec les collections existantes.

les VEMs permettent donc de faire évoluer les librairies existantes tout en assurant la compatibilité avec le code existant.

On peut bien sur utiliser des lambdas avec ses propres interfaces (pourvu qu’elles aient une seule méthode abstraite), ce qui va permettre de créer des librairies acceptant des expressions lambdas comme paramètres.

L’utilisation des VEM permet de plus d’améliorer facilment les APIs vues par le client et de créer des API “fluent” avec du chainage de méthodes, etc.

Enfin, d’un point de vue outilage, il reste encore aujourd’hui beaucoup de progrès à faire. Les IDEs ne proposent pas encore de support permettant de coder confortablement avec ces nouveautés du JDK8 (ni les expressions lambdas ni les VEMs).

Maven par contre se contente de déléguer la compilation et l’exécution aux binaires javac et java, et l’utilisation du JDK8 avec maven est très transparente.