IMG_20160420_132448-PANO

J’ai eu la chance cette année de participer pour la première fois à DevoxxFR. J’ai pu assister à beaucoup de conférences sur le monde Java mais aussi à d’autres traitant du web, de la sécurité, de l’intégration continue, des outils utilisés…

Le seul regret est de ne pas pouvoir assister à toutes les conférences, heureusement elles sont disponibles sous forme de vidéos : https://www.youtube.com/channel/UCsVPQfo5RZErDL41LoWvk0A

De mon point de vue les gros sujets de cette année étaient liés à Java 8 et Java 9, je vais donc vous parler de la conférence réalisée par Jean-Michel Doudoux (Java Champion) sur son retour d’expérience concernant Java 8.

Retour d’expérience sur Java 8

hdr

Java 8 c’est plus que les Date, les lambdas et les Streams. 2 ans se sont écoulés depuis sa sortie. Pour Jean-Michel, Java 8 est la plus ambitieuse version de Java.

Dans cette conférence il insiste sur l’utilisation des bonnes pratiques, comment peut-on les définir :

  • Empiriques et subjectives
  • Contextuelles
  • Mouvantes : rééavulation périodique. Hardware et JVM évoluent.
  • Concernent plusieurs facteurs : maintenabilité, performance, style de code
  • Ce ne sont pas des règles rigides. On ne doit pas les appliquer bêtement.

Repassons en revue les nouveautés de la version 8 de Java (pour ceux qui seraient passés à côté ces derniers mois) :

Les Optional  :

Classe qui encapsule une valeur ou l’absence de valeur. L’utilisation d’Optional rend le code plus robuste (au détriment de la performance).

Limitations : classe finale et pas sérialisable

C’est le sujet le plus controversé alors qu’il ne s’agit que d’une classe. La raison est qu’elle est souvent assimilée à d’autres langages.

Utilisation comme valeur de retour :

  • Nécessaire dans certaines circonstances
  • Cas d’utilisation officiel (cf. Brian Goetz)
  • A éviter dans les getters de bean

Utilisation dans les paramètres :

  • Pas recommandée car pollue la signature
  • Plus complexe pour l’appelant.
  • Contournement : possibilité d’utilisé la surchage de méthode pour éviter de passer des paramètres null.

Utilisation comme variable d’instance :

  • A éviter
  • Support par certains frameworks

Utilisation comme variable locale :

  • Jamais

Bonne pratiques :  

  • Passer par une fabriques of(), ofNullable(), empty()
  • Essayer de limiter le caractère optionnel d’une valeur
  • Pour les primitifs : OptionalInt, OptionLong …
  • Attention à l’utilisation de la méthode get() => NoSuchElementException si pas de  valeur. Utilisation de orElse() pour passer une valeur par défaut.
  • Eviter d’utiliser Optional avec une collection ou un tableau => utiliser une collection  ou un tableau vide avec isEmpty() ou length()

Parallel arrays  

  • Méthodes Arrays.parallelXXXX
  • Exemple sur un tableau de 20 millions d’entrées alimentées avec des nombres aléatoires :

              o La méthode setAll() de Java8 permet d’initier un tableau avec une lambda =>  plus compact pour des perfs équivalentes

              o La parallélisation est moins performante car classe Random thread-safe. Utiliser  ThreadLocalRandom de Java 9.

Date & Time

  • Enfin une API riche et complexe
  • Thread-safe car classes immuables

Bonnes pratiques :

  • Ne plus utiliser Date/Calendar ni Joda Time
  • Bien choisir le type à utiliser selon les données temporelles requises
  • Lors de la déclaration de variables, ne pas utiliser les interfaces (ex : Temporal), mais  directement les classes (ex : DateTime)
  • Utilisation des TemporalAdjuster
  • Classe Clock :

              o Facilite les tests automatisés

              o Par défaut, renvoie la date/heure système

              o Pour les tests, injecter une instance obtenue par Clock.fixed(). Permet de fixer  le temps pour avoir des tests reproductibles

 

Lambda

Fonction anonyme : fournit une implémentation pour une interface fonctionnelle (SAM) exemple : Java.util.function Function, Predicate, Consumer, Supplier…

Un nouvel opérateur apparait : ->

 

Une lambda a accès aux variables effectivement finales. Plus besoin d’utiliser le mot clé final.

Bonnes pratiques :

  • Utiliser les lambdas à la place des classes anonymes internes
  • Privilégier

o L’inférence de types (le compilateur s’en occupe)

o L’utilisation des interfaces fonctionnelles fournies par le JDK

  • A noter ces interfaces fonctionnelles avec @FunctionalInterface => utilisées par le  compilateur et la javadoc
  • Si l’expression est l’invocation d’une méthode, utiliser les références de méthodes
  • Garder les expressions lambdas les plus simples possibles

o Eviter des blocs de code

  • Les checked exception sont difficilement intégrables
  • Les exceptions levées par une expression doivent être déclarées dans l’interface  fonctionnelle.

 

API Stream

  • Exécution de traitement sur une séquence d’éléments

o Obtenus d’une source finie ou infinie

o Exécution d’un pipeline d’opérations

o Exécution séquentielle ou en parallèle

  • Requiert de réfléchir en “fonctionnel”

 

Bonnes pratiques :

  • Attention à l’ordre des opérations intermédiaires

o Ex : filter() + sorted() vs sorted() + filter() => performances

  • Ne pas abuser des Streams
  • Bien adapter pour les collections, moins pour les map
  • Limiter l’utilisation du forEach (raisonnement impératif et pas fonctionnel)
  • Déboguer un Stream :
    • Plutôt difficile
    • Utiliser la méthode peek()
    • Ou utiliser une référence de méthode + point d’arrêt

Avec des données primitives, utilisez par DoubleStream, IntStream … il y a de gros impacts sur  les perfs à cause de l’auto-boxing

  • Utilisation des Stream infinis

o Penser à utiliser des limites (ex : limit())

  • Les Streams parallèles

o Facilité de mise en œuvre qui ne rime pas forcément avec performance

o Utilise le framework Fork/Join et son pool par défaut

o Les opérations intermédiaires de type stateful vont dégrader un peu les perfs

o Attention au Spliterator

  • Certaines sources de données (ex : LinkedList et I/O) sont peu performantes

o Attention aux Collectors

  • Performance (ex : grouping())
  • Utilisation d’objets ayant un support de la concurrence => pas de  HashMap
  • Doit être compensé par le volume de données à traiter

La version 8 de java apporte énormément de choses, cependant migrer les projets en Java 8 demande un minimum de connaissances surtout au sujet de la programmation fonctionnelle afin d’en exploiter toute la puissance.

 

Concatenation de String

J’ai pu assister aussi à une conférence animé par Remy Forax concernant la concaténation de String.

La concatenation de chaine de caractères est une des premières choses que l’on montre à un débutant, pourtant même si cela repose sur un ensemble de mécanismes assez simples (+, StringBuilder et toString), la façon de concaténer des chaines de caractères a changé au fil des versions de Java. Point culminant l’implémentation à base d’invokedynamic recemment introduite dans la version 9.

En utilisant le prétexte de la concaténation de chaine de caractères, cette présentation explique le fonctionnement habituel d’une feature en Java, avec le rôle du compilateur, le rôle du JDK, le rôle de la machine virtuelle et de ses JITs.

Rémy utilise javap 1.8 pour regarder le bytecode : le compilateur est malin et utilise lui même un StringBuilder. C’est le cas depuis la version 1.0 de Java.  Le bytecode est facile à lire mais çà ne sert à rien. En cas d’optimisation, la JVM transforme le bytecode en code assembleur. On peut demander à la JVM d’afficher ce que le JIT compile.  JIT s’arrête lorsque le code à compiler est trop gros.  On peut également demander le code assembleur généré : illisible car trop de lignes (Option par défaut –XX :OptimizeStringConcat).

Le JIT optimise le code grâce à des patterns Java, comment trouver les patterns dans le code Java ? On prend un certain nombre d’applications et on regarde comment les développeurs ont  développés. Ces applications sont les serveurs d’applications des années 90. D’après Rémy, « Plus on code comme les autres, plus le code ira vite » …

Des optimisations ont été faites dans Java 9,  à cause de la JEP 254 les chaines ne sont plus codées avec des chars, mais avec des tableaux de bytes. Il y a un paramètre supplémentaire qui indique l’encodage (LATIN, UTF16). C’est un enorme travail de réimplémenter String car il y a des méthodes (ex : equals) qui sont codées en assembleur. La plupart des grosses applications utilisent énormément de String.

Depuis Java 8, des efforts ont été effectués pour améliorer les performances. Le GC (Garbage Colector) G1 sait dé-dupliquer les String. Lorsqu’on fait de la concaténation entre 2 chaînes, il faut se demander si les 2 chaînes ont le même encodeur (8 bits vs 16 bits). Si c’est différent, il faut changer l’encodeur. C’est le code assembleur supplémentaire. Pour bénéficier de cette amélioration introduite dans la JEP 280, il faudra recompiler le code source. Autre inconvénient : les langages de la JVM autres que Java (ex : Scala) devront utiliser ce modèle. Il y a des risques de régression.

Règle et bonnes pratiques : Il faut gérer les StringBuilder à la main uniquement pour les boucles, sinon il faut utiliser l’opérateur ‘+’.

Pour plus d’information sur ce sujet la video est disponible ici : https://www.youtube.com/watch?v=U97zT8kA7G4

 

A l’heure où la plupart des entreprises tourne en java 6 ou 7 et ne sont pas encore migrée en version 8, on parle de plus en plus de la prochaine version : Java 9 qui va apporter de gros changements. Le plus important de ces changements s’appellent le projet Jigsaw.

Le projet Jigsaw promet de rendre la JVM modulaire, c’est à dire découpée en plusieurs “java modules” afin de n’utiliser que le strict nécessaire pour votre application ou simplement éviter ce que l’on nomme parfois “Jarmageddon” ou “Jar-Hell”.

Initialement prévu pour Java 8, puis Java 9, et puis finalement pour “Java 9 bis”, ce n’est clairement pas pour tout de suite, mais étant donné la portée du sujet il est intéressant de s’y pencher tout de suite.

hdr

Apache Maven, Java 9 et le projet Jigsaw

Arnaud Héritier et Hervé Boutemy, contributeurs au projet Maven, nous ont présenté l’avancement de l’intégration des évolutions (essentiellement Jigsaw) apportées par Java 9 (release prévue en avril 2017) à Maven. Java 9 est compilé en version 8 (52.0) actuellement ce qui va être corrigé pour passer à la version 53.0.

Les speakers ont passé en revue quelques nouveautés que va apporter Java 9 :

  • [JEP 223] New Version-String Scheme : la numérotation des versions change à partir de Java 9 qui sera véritablement la version 9.x.x et plus 1.7.0_27
  • [JEP 224/225] New Javadoc Style : un nouveau champ de recherche a notamment était ajouté à la Javadoc désormais compatible HTML5/CSS3 !
  • [JEP 238] Multi-Release JAR Files : Il sera possible en Java 9 de combiner dans un jar du code java 7 ou 8 qui sera sélectionné selon la version du JRE utilisé à l’exécution.
  • [JEP 247] Compile for Older Platform Versions : une nouvelle option du compilateur -release permet de compiler avec le rt.jar du JDK 7/8

Actuellement, il existe deux versions du JDK 9 :

  • Classique : nouveautés JDK 9 sans les modules
  • Jigsaw (seule cette version sera releasée)

Le projet Jigsaw (qui traîne depuis quelques années) ajoute la notion de modules au JDK qui permettra d’avoir enfin un mécanisme de visibilité fiable et un moyen d’expression des dépendances. Ces évolutions seront appliquées au JDK lui-même et c’est donc la mort du fameux rt.jar. (Pour ceux qui ne connaissait pas l’existence de ce jar : http://javarevisited.blogspot.com/2015/01/what-is-rtjar-in-javajdkjre-why-its-important.html)

On trouvera un fichier « module-info.java » de la forme suivante :

module com.foo.bar {
 requires org.baz.quz
 exports com.foo.bar.xxx
 exports com.foo.bar.yyy
}

Il existe une notion de « automatic module » qui va permettre de dépendre quand même d’un package qui n’aurait pas été exporté dans un module. Java va automatiquement détecter et gérer ce genre de cas. Côté Maven, de nombreux plugins vont devoir évoluer. Les travaux en collaboration avec Oracle pour adapter Maven à Jigsaw ont bien simplifié la tâche. Maven a déjà géré un grand nombre de problèmes et fonctionne avec Jigsaw.

L’un des enjeux pour Maven était de répondre à la JEP 238 apportant les Multi-Release JAR Files. Pour y répondre, le plugin maven-multimodule va permettre de détecter les différents JDK sur lesquels le code doit être buildé. Il va créer un sous-module par version de JDK détectée et le maven assembly va coordonner tout ça correctement.

Une des questions que l’on peut se poser est l’avenir du pom.xml face au nouveau module-info.java. Le pom.xml n’est pas voué à disparaître avec Jigsaw. Le module-info.java porte quelques infos supplémentaires et différentes du pom.xml, mais ne vient pas en remplacement de ce dernier qui apporte bien d’autres fonctionnalités.

Autre nouveautés de Java 9, la JavaDoc passe au html5 et intègrera désormais une barre de recherche ! Les deux JEP principales : la JEP261, Module System (alias Jigsaw pour les intimes) et la JEP238, aka Multi-Release JAR Files.

 

Pour conclure il reste un peu moins d’une année avant la sortie de Java 9. Le numéro de version a complètement changé, on n’utilise plus désormais 1.9 mais 9 directement. Il risque du coup de falloir adapter quelques utilitaires.