Passer au contenu principal

Bon OK on a deux ans de retard, enfin presque, mais de toute comme Java 8 n’est pas encore très présent sur le marché du travail ça ne pose pas vraiment de problème. Bref on va voir dans cet article les principales nouveautés apportées par cette itération.

Les principales nouveautés de Java 8 sont détaillées dans l’excellent livre Java 8 in action, à savoir :

  •  Les lambdas
  •  Les streams sur les collections

Les lambdas

Les lambdas représentent une nouvelle manière d’écrire du code, de manière plus concise qu’avant. Dans les faits ce n’est pas tout à fait ça, ça se voit si vous avez une exception dans votre lambda, mais on va dire que c’est ça.

Prenons le code suivant :


button.addEventListener(new ActionListener() {

   public void actionPerformed(ActionEvent e) {
      System.out.println("Click");
   }

});

Il est clair qu’il est inutilement lourd par rapport à ce qu’on veut faire, à savoir que quand on clique sur un bouton le message « Click » s’affiche sur la console. Avec les lambdas, on pourrait l’écrire de la sorte :


button.addEventListener(e -> System.out.println("Click"));

C’est franchement plus concis, pas vrai ?

Vous me direz, mais comment la JVM fait-elle pour s’y retrouver et savoir quelle méthode instancier ? La réponse tient en deux mots : interface fonctionnelle. Une interface fonctionnelle est une interface qui ne définit qu’une seule méthode.

Par exemple java.awt.event.ActionListener est une interface fonctionnelle car elle ne définit qu’une seule méthode, par contre java.util.Iterator n’en est pas une.

Quelques trucs à savoir sur les lambdas

Mettons qu’on ait l’interface suivante :


public interface MathOperation {

   public double compute(double a, double b);

}

Une implémentation de cette opération pourrait être l’addition. Sous forme de lambda elle serait écrite de la sorte :


(a, b) -> a + b

Par contre si on voulait que notre méthode soit un peu plus complexe, la syntaxe de la lambda s’alourdirait notamment avec l’usage du mot-clef return qui est facultatif dans le cas où la lambda ne contient qu’une seule opération. Par exemple on pourrait avoir :


(a, b) -> {
   double res = a + b;
   System.out.println(res);
   return res;
}

Les références de méthodes

Les références de méthodes sont aussi un truc sympa de Java 8 introduit par les lambdas.

Mettons qu’on ait un tableau de String dont la déclaration est comme suit :


String[] myArray = {"one", "two", "three", "four"};

Il est parfaitement possible de le trier avec la notation suivante :


Arrays.sort(myArray, String::compareToIgnoreCase);

Il est possible de passer des références de méthodes statiques, ou des références de méthodes d’instance. Dans le deuxième cas, la méthode d’instance doit bien évidemment être définie pour chacun des éléments pour lesquels la lambda doit être appliquée.

Lambdas : attention au débogage

Les lambdas reposent en fait sur l’appel invokedynamic introduit depuis Java 7 pour les langages comme Scala ou Groovy. Le problème est que les lambdas sont relativement complexes à déboguer, comme l’illustre parfaitement cet exemple. En particulier les piles d’appel sont souvent assez immondes à déboguer, et on perd facilement la trace entre l’appelant de la lambda et cette dernière.

C’est pourquoi il ne faut jamais perdre de vue les éléments suivants quand on utilise les lambdas :

  1.  Tout d’abord elles doivent être concises. Une ou deux instructions maximum.
  2.  Ensuite elles doivent être relativement simples à comprendre pour que leur débogage reste trivial.

Les streams

Les streams sont un ajout au framework Collections en Java 8. Ils permettent en particulier d’utiliser les systèmes de map/reduce sur ces collections. On ne va pas détailler ici toutes les fonctionnalités de l’API stream, je vous renvoie vers la javadoc de cette API.

Le map/reduce

Comme indiqué, un map/reduce se divise en deux étapes :

  • – Tout d’abord la phase de map, qui consiste à appliquer une ou plusieurs opérations à chaque élément de la collection.
  • – Ensuite la phase de reduce, qui est utilisée pour récupérer le résultat. Ce dernier peut être une collection, un objet, ou n’importe quoi d’autre.

L’avènement de ce type de programmation a eu lieu avec les processeurs multi-cores. Un exemple simple consiste par exemple à multiplier tous les éléments d’une liste par 5. En fonctionnement itératif simple, on bouclerait sur chaque élément de la liste, puis on multiplierait chacun des éléments par 5. En map/reduce, on peut par contre opérer de la manière suivante :

  1. Diviser la liste en autant de part qu’il y a de cores sur la machine
  2. Attribuer chaque sous-liste à un cores, qui multiplie par 5 chacun des éléments de sa sous-liste
  3. Enfin rassembler les listes calculées

Le gain de performances vient du fait que le temps de calcul est cette fois divisé par le nombre de cores sur la machine. Implémenter une telle division en itératif serait très complexe.

Les pièges

Comme on l’a vu le map/reduce introduit avec les streams permet dans certains certains cas d’améliorer grandement les performances. Mais attention, cela ne fonctionne que dans le cas où il est possible de partitionner la collection pour faire les opérations demandées. Dans le cas contraire on peut au contraire perdre en performances.

Bref le meilleur moyen de savoir quelle implémentation est meilleure pour résoudre telle ou telle problématique est d’expérimenter. Tant que vous ne rencontrez pas de souci de performances avec votre code ne faites rien mais si un tel souci devait se faire sentir une solution pourrait passer par les streams et le map/reduce.

En bref

Comme on l’a vu, les lambdas permettent de grandement simplifier certaines notationCONCs. Attention toutefois à ne pas en abuser car le code peut être rendu assez pénible. De leur côté les streams permettent de remettre la plateforme Java au goût du jour en profitant de nos machines multicores récentes. Mais attention, il ne s’agit pas d’une recette miracle mais d’une solution qui peut être utile dans certains cas.

En attendant même si vous ne souhaitez pas utiliser le map/reduce ni les lambdas, passer à Java 8 peut être bénéfique pour bénéficier de diverses améliorations de la JVM en terme de performances et de sécurité, comme à chaque nouvelle version de cette plateforme.

Besoin de tester ton niveau en développement informatique ?

Cet article vous a plu ? Vous aimerez sûrement aussi :

Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.

Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.

Son Twitter Son LinkedIn

Rejoignez la discussion Un commentaire

  • adiGuba dit :

    Ce n’est pas les expressions lambda qui sont « complexe » à déboguer, mais la nouvelle API de Stream.
    Ce serait la même chose si on utilisait des classes anonymes.

    Et encore complexe c’est un bien grand mot. Comme toujours il suffit de remonter le stacktrace jusqu’à la ligne correspondant dans notre code…

    a++

Laisser un commentaire