2
Moi
MCF à l'Université Paris Est Marne-la-vallée
Joue avec Java depuis trop longtemp pour
l'avouer
Créateur de langages dynamiques ou pas
Expert pour les JSR 292 (invokedynamic) et
JSR 335 (lambda)
Contributeur OpenJDK, ASM, Tatoo, PHP.reboot, JDart,
etc...
3
Un exemple de lambda
enum Gastronomy {
rosette,
andouillette,
coq_au_vin,
tripe,
cardoon
}
private static List<Gastronomy> getGastronomyList() {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
Collections.sort(list, new Comparator<Gastronomy>() {
@Override
public int compare(Gastronomy g1, Gastronomy g2) {
return g1.name().compareTo(g2.name());
}
});
return list;
}
public static void main(String[] args) {
System.out.println(getGastronomyList());
}
4
Exemple un poil plus compliqué
private static ArrayList<Gastronomy> prefixList(String prefix, List<Gastronomy> list) {
ArrayList<Gastronomy> list2 = new ArrayList<>();
for(Gastronomy gastronomy: list) {
if (gastronomy.name().startsWith(prefix)) {
list2.add(gastronomy);
}
}
return list2;
}
private static List<Gastronomy> getGastronomyList(String prefix) {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
ArrayList<Gastronomy> list2 = prefixList(prefix, list);
Collections.sort(list2, new Comparator<Gastronomy>() {
@Override
public int compare(Gastronomy g1, Gastronomy g2) {
return g1.name().compareTo(g2.name());
}
});
return list2;
}
6
Closure ??
Expression que l'on veut voir comme une valeur
Les closures/lambda existent dans plein d'autres
langages
Lisp/Clojure, Ruby, Groovy, Scala, C#,
JavaScript/Python, et même C++/Objective C
récemment
En Java, on a des classes annonymes
Youpi !
Nan, je rigole :)
7
Problème des classes anonymes
Verbeux visuellement
rapport signal/bruit pas terrible
Sémantiquement bizarre
on veut envoyer une expression, créont une classe ...
Perf discutable (pour des closures)
création d'une instance à chaque appel
+ 1 classe sur le disque
+ 1 instance de java.lang.Class et
ses métadatas en mémoire
8
Exemple de lambdas
(sans lambdas)
private static List<Gastronomy> getGastronomyList(String prefix) {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
ArrayList<Gastronomy> list2 = prefixList(prefix, list);
Collections.sort(list2, new Comparator<Gastronomy>() {
@Override
public int compare(Gastronomy g1, Gastronomy g2) {
return g1.name().compareTo(g2.name());
}
});
return list2;
}
9
Exemple de lambdas
(avec lambdas)
private static List<Gastronomy> getGastronomyList(String prefix) {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
ArrayList<Gastronomy> list2 = prefixList(prefix, list);
Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> {
return g1.name().compareTo(g2.name());
});
return list2;
}
La syntaxe utilise -> (light arrow)
et pas => (fat arrow) comme en Scala ou en C#
10
Inférence de type !
Le compilateur peut calculer le type des paramétres d'une
lambda !
la méthode sort() prend une liste de Gastronomy
donc elle attends un Comparator<Gastronomy>
private static List<Gastronomy> getGastronomyList(String prefix) {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
ArrayList<Gastronomy> list2 = prefixList(prefix, list);
Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> {
return g1.name().compareTo(g2.name());
});
return list2;
}
Le compilo n'utilise pas l'expression de la lambda pour faire l'inférence !
12
Syntaxe: Lambda expression
sans paramètre
() -> System.out.println("Vive l'OL")
avec un paramètre (+ inférence)
employee -> employee.isManager()
avec plusieurs paramètres
en déclarant les types
(int x, int y) -> x == y
sans déclarer les types (+ inférence)
(x, y) -> x == y
13
Syntaxe: Lambda instruction
sans paramètre
() -> {
System.out.println("Vive l'OL");
}
avec un paramètre (+ inférence)
employee -> {
return employee.isManager();
}
avec plusieurs paramètres (+ inférence)
(index1, index2) -> {
list.set(index1, list.get(index2));
}
14
Sémantique
Typé par une functional interface (ex SAM)
Runnable, Callable, Filter, Function, ...
Une lambda n'est pas un objet
“this” représente la classe courante pas la lambda
Une lambda est convertissable en un objet
qui implante une functional interface
Runnable r = () -> System.out.println("LOL?")
objet pas objet
15
Et les collections ?
Ici on filtre une liste, donc au lieu de
private static ArrayList<Gastronomy> prefixList(
String prefix, List<Gastronomy> list) {
ArrayList<Gastronomy> list2 = new ArrayList<>();
for(Gastronomy gastronomy: list) {
if (gastronomy.name().startsWith(prefix)) {
list2.add(gastronomy);
}
}
return list2;
}
16
Et les collections ?
On aimerait bien écrire
private static List<Gastronomy> prefixList(
String prefix, List<Gastronomy> list) {
return list.filter(g -> g.name().startsWith(prefix))
.toList()
}
Operation lazy
Operation pas lazy
Heu, toList() sur une liste, vraiment ??
18
Programmation déclarative
Permet la programmation parallèle
On split les données en amont en on envoie le calcul sur
différentes threads
● Disponible pour Java 8
Permet d'utiliser les GPUs
On émet le code PTX/HSAIL correspondant
● Projet Sumatra (prototype avec Graal)
With great power there must also come — great
responsibility!
L'utilisateur ne doit pas faire d'effet de bord dans
les lambdas !!
19
Interface java.util.Stream
Sequentielle ou parallele
list.stream() ou list.parallelStream()
Deux types de methode
intermédiaire
filter, map, sorted, distinct, flatMap ...
terminales
forEach, reduce, collect, findFirst ...
L'implantation n'utilise pas de structures
intermédaires mais un pipeline !
Un effet de bord, t'es mort !
20
Sans l'API des streams
private static ArrayList<Gastronomy> prefixList(
String prefix, List<Gastronomy> list) {
ArrayList<Gastronomy> list2 = new ArrayList<>();
for(Gastronomy gastronomy: list) {
if (gastronomy.name().startsWith(prefix)) {
list2.add(gastronomy);
}
}
return list2;
}
21
Avec l'API des streams
private static ArrayList<Gastronomy> prefixList(
String prefix, List<Gastronomy> list) {
return list.stream()
.filter(g -> g.name().startsWith(prefix))
.collect(Collectors.toList());
}
Bonus! pas besoin de déclarer la variable locale final !
23
Traits
Pour ajouter le support des lambdas au
collections
Les méthodes stream() et parallelStream()
Il faut pouvoir ajouter des méthodes à une
interface
Il faut mettre du code dans les interfaces
Don't panic
Cela s'appelle des traits
(trivia: les traits de Scala ne sont pas des traits)
24
Default methods
Une méthode qui a du code dans une interface doit
être taggée default
Une méthode par défaut est utilisée par une classe si il
n'y a pas de méthode définie ou fournie par une sous
classe
public interface Iterator<T> {
public abstract boolean hasNext();
public abstract T next();
public default void remove() {
throw new UnsupportedOperationException();
}
}
25
Et le problème du diamand
Une interface accepte du code
mais pas de champs
Pas de problème de redéfinition de champ donc
Problème si on implante deux interfaces qui ont
chacune une méthode ayant du code
– Si une interface hérite de l'autre, on prend la méthode de la
sous-interface
– Sinon, le compilo plante
interface I { default void m() { } }
interface J { default void m() { } }
class A implements I, J {
// compile pas
}
26
Super sur des interfaces
La syntaxe, I.super.m() permet d'appeler la
méthode m de l'interface I.
donc pour résoudre le conflit
interface I { default void m() { } }
interface J { default void m() { } }
class A implements I, J {
public void m() {
I.super.m();
}
}
27
Retrofit
Avec Java 8, l'interface java.util.List possède enfin une
méthode sort (par défaut)
qui appele Collections.sort :)
private static List<Gastronomy> getGastronomyList(String prefix) {
List<Gastronomy> list = Arrays.asList(Gastronomy.values());
ArrayList<Gastronomy> list2 = prefixList(prefix, list);
list2.sort((g1, g2) -> g1.name().compareTo(g2.name()));
return list2;
}
28
Avoir une méthode intermédiaire n'est plus nécessaire, on peut
tout écrire dans une seule méthode
Et en utilisant Comparators.comparing()
private static List<Gastronomy> getGastronomyList(String prefix) {
return Arrays.stream(Gastronomy.values())
.filter(g -> g.name().startsWith(prefix))
.sorted(Comparators.comparing(g -> g.name()))
.collect(Collectors.toList());
}
public static void main(String[] args) {
System.out.println(getGastronomyList("c"));
}
Exemple (suite)
29
Method Reference
Il existe une syntaxe spécifique pour les
lambdas qui appel juste une méthode
g -> g.name() peut s'écrire Gastronomy::name
On ne spécifie pas le type des paramètres,
le compilateur utilise la fonctional interface pour
les trouver
Il est aussi possible de capturer une valeur
Callable<String> c = "foo"::toUpperCase()
System.out.println(c.call()); // prints FOO
30
Avec des Method References
Et tout dans le main.
public static void main(String[] args) {
Arrays.stream(Gastronomy.values())
.filter(g -> g.name().startsWith("c"))
.sorted(
Comparators.comparing(Gastronomy::name))
.forEach(System.out::println);
}
32
Compilation - Création
Une référence sur une méthode est crée en
utilisant invokedynamic
BiFunction<String, Integer, Character> fun
= String::charAt;
est transformé par le compilateur en
code:
0: invokedynamic lambda() #0
()Ljava/util/function/BiFunction;
5: astore_1
34
Intérêt d'utiliser invokedynamic
La création de l'objet implantant la functional
interface n'est pas écrite dans le code généré
Peux changer en fonction des versions du JDK,
des optimisations implantées dans la VM
Si l'objet est constant, il peut être ré-utilisé
Pour le jdk8, utilise ASM pour générer une
classe proxy dynamiquement
et sans vérification du bytecode
35
Compilation - Appel
L'appel se fait en utilisant la méthode de l'interface
BiFunction<String, Integer, Character> fun = ...
char c = fun.apply("foo", 0);
et en bytecode
7: ldc #3 // String “foo”
9: iconst_0
10: invokestatic #4 // Integer.valueOf:(I)Ljava/lang/Integer;
13: invokeinterface #5, 3 // BiFunction.apply
(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
18: checkcast #6 // class Character
21: invokevirtual #7 // Character.charValue:()C
24: istore_2
36
Et les perfs ? / un nano-test
public class HoodPerf {
private static int COUNTER;
private static void test() {
BiFunction<String, Integer, Character> fun = String::charAt;
char c = fun.apply("foobar", 5);
COUNTER += c; // side effect
}
public static void main(String[] args) {
for(int i=0; i<10_000; i++) {
test();
}
System.out.println(COUNTER);
System.out.println(end – start);
}
}
Ce code ne marche
pas pour faire un
benchmark mais ...
40
... en résumé
Une méthode référence (ou une lambda) qui ne
fait pas de capture est une constante
L'appel a une lambda peut être dévirtualisé et
inliné comme n'importe quel appel
Le code montré est celui générer par le
jdk8b88-lambda avec elision du
boxing/unboxing desactivé :(
41
What's next ?
Updates du JDK8
lambda constante sans check de classe
lambda non-constante mieux dévirtualisée
Java 9
Value Object
Integer, Float, etc ne devrait pas avoir d'identité
=> pas sûr que cela marche
Utilisation du/des GPUs dans les Stream parallele
42
Questions ?
JDK8 Feature Freeze jeudi prochain !
les lambdas sont déjà dans le JDK !
https://jdk8.java.net/download.html
Remonter les bugs sur
la mailing list: lambda-dev
http://mail.openjdk.java.net/mailman/listinfo/lambda-dev
ou bugs.sun.com