DevoxxFR 2014 : Java 8 ou Java 7+1

Commençons par regarder derrière nous : qu’avez-vous retenu de Java 5 ? 6 ? 7 ? Cette lignée marque l’âge de maturité pour le langage et l’ouverture aux langages bytecode compatibles. La version 8 continue d’évoluer et va vous faire rêver, même si vous utilisez déjà des fonctionnalités proches via l’ajout de frameworks alternatifs comme JodaTime, Guava, Scala, etc.

Lors de l’édition 2014 de Devoxx France, plusieurs sessions ont mis en valeur la version 8 de Java notamment celle de José Paumard et celle de Paul Sandoz.

Cette nouvelle version est une évolution majeure du langage Java. Elle apporte en effet du fonctionnel dans le langage. Ce paradigme de programmation est différent et dérangeant (dans un premier temps pour les adorateurs des objets) mais permet de réaliser des tâches plus simplement, plus efficacement. Le JCP a réussi le tour de force de rendre énormément de fonctionnalités accessibles, compatibles et presque simples ! La version 8 est donc portée sur la parallélisation des traitements et l’utilisabilité.

Les évolutions majeures se nomment :

  • Lambdas et streams,
  • JavaTime,
  • Java FX, pour la parte UI,
  • JSR-292, intégration d’un moteur Javascript dans la JVM,
  • Concurrency pour la gestion de la concurrence.

Nous nous attarderons sur les deux premiers points qui représentent les évolutions les plus importantes.

Lambdas et Streams

Un stream est un tuyau permettant de traiter efficacement des données. C’est particulièrement efficace en traitement en parallèle pour exploiter les processeurs multicoeurs et en pipeline pour éviter les intermédiaires de calcul. Énorme avantage, nul besoin de connaître 50 nouvelles classés, il suffit d’utiliser les objets à disposition. Typiquement une Collection peut être vue comme un stream si l’on prend ses éléments les uns après les autres.

Du code plutôt que de long discours :

List<Person> persons = initList();
Stream<Person> stream = persons.stream();
stream.forEach(System.out::println);

Ce code permet d’appliquer un traitement sur l’ensemble des éléments d’une liste, comme dans Guava. Cela ouvre la porte aux traitements simples de données en tableau et surtout cela permet d’ajouter le mot clé parallel pour expliciter l’utilisation de plusieurs threads afin de réaliser une opération en masse. D’autres méthodes sont disponibles pour les streams comme filter, map, reduce, peek, etc.

A l’image des pipes des lignes de shell, les streams peuvent être combinés sous forme de fluent API.

stream.filter(a -> a.getName().indexOf("User")!=-1)
.forEach(System.out::println);

Mais il nous manquait un moyen simple d’exprimer les traitements à effectuer. L’annotation @FunctionalInterface a été conçue dans cette optique. Elle permet de définir une classe anonyme ayant la particularité de n’avoir qu’une méthode (hors méthode d’Object) tout en restant totalement compatible avec du code Java antérieur.

Par exemple :

@FunctionalInterface
public interface MyFilterInterface<T> {
    boolean accept(T value);
}
MyFilterInterface<Person> filter = p -> p.getName().charAt(0)=='U';
stream.filter(filter::accept).forEach(System.out::println);

 

Les lambdas s’accompagnent d’une nouvelle syntaxe simplifiant leurs écritures et en la condensant. On veut exprimer une transformation pour des arguments donnés ; les langages fonctionnels sont basés sur ce concept.

1. (a,b,c)            représentent les arguments de la fonction
2. →                 sépare les arguments du corps de la fonction
3. a+b+c            le code de la fonction

La syntaxe est encore simplifiée pour les méthodes déjà existantes, qui peuvent être réutilisées avec une syntaxe  lambda. Par exemple, la méthode println (du package System.out) est invocable sur des string. On condense l’écriture en  System.out::println au lieu de s → System.out.println(s).

Comme pour les classes anonymes, il n’est pas possible d’accéder aux variables non final (même si la syntaxe le laisse croire. Une variable hors du scope de la classe anonyme n’est pas modifiable et est final d’office).

Avec ce concept de base, les lambdas se sont enrichis avec des interfaces particulières :

  • Predicate renvoie un booléen à partir d’un unique argument (s) -> s.length() > 0,
  • Function renvoie un objet à partir d’un unique argument Integer::valueOf,
  • Supplier renvoie un objet, sans argument Person:new,
  • Consumer ne renvoie rien à partir d’un unique argument System.out::println,
  • Comparator renvoie un booléen à partir de 2 objets de même type, à l’image de l’interface Comparator existant dans Java.

Les streams peuvent s’appuyer sur ces différentes formes afin de généraliser des traitements. Par exemple forEach prend un consumer en argument, filter un Predicate.

Java 8 arrive ainsi avec un ensemble cohérent de fonctionnalités autour des lambdas et des streams pour rendre les développeurs opérationnels rapidement. On remarquera que si les concepts de programmation fonctionnels sont juste effleurés, la syntaxe reste lisible et facile d’accès.

Javatime

Depuis sa création Java est embarrassé avec son API de gestion du temps. Même après avoir ajouté Calendar, l’API reste obscure, complexe à utiliser et très peu lisible. Depuis que le projet JodaTime est né, il a mis en avant une API plus lisible et plus explicite. C’est sur ce même modèle qu’est né JavaTime.

Les concepts utiles sont exprimés à travers des classes permettant de faciliter leur compréhension et leur manipulation :

  • Clock représente une horloge (attachée à une timezone), un couple date/time.
    Par exemple, le 24 Mai 2014, 11h30 à Paris
  • Timezone représente une zone temporelle.
    Par exemple, Europe/Paris
  • LocalTime représente un timestamp, sans timezone.
    Par exemple, 11h30
  • LocalDate représente une date, sans timezone.
    Par exemple, 24 Mai 2014
  • LocalDateTime représente la concaténation des deux précédents.
    Par exemple, le 24 Mai 2014  11h30

Pour manipuler les dates, il est plus simple de s’appuyer sur l’API car les concepts sont plus clairement définis.

Par exemple, Duration représente une durée temporelle, qu’elle soit exprimée en secondes, heures, ou autre. Nous ne sommes plus obligés de jongler avec des noms des variables pour connaître leurs unités.

La JSR-310 apporte également une manipulation native en nanosecondes.

En conclusion, cette nouvelle API oblige les développeurs à manipuler les dates correctement. Au-delà de la lisibilité, elle limitera d’autant plus les possibilités d’anomalies liées aux traitements des dates.

Consultant Java

Inscription newsletter

Ne manquez plus nos derniers articles !