Java 11, sorti en septembre 2018, est une nouveauté majeure du langage Java en ce qu’elle constitue une version LTS (Long Time Support) qui sera soutenue par Oracle jusqu’en 2023 – et même au-delà si on souhaite bénéficier d’un support étendu. De ce fait, Java 11 apparaît comme une version incontournable à l’horizon des prochaines années. Dans cet article, je vous propose de vous familiariser avec une sélection de 9 nouveautés contenues dans le JDK 11. 

1. Inférence de type pour les arguments des expressions lambda

Cette évolution s’inscrit dans la continuité de l’inférence de type des variables ajoutée avec Java 10. Il est désormais possible de ne pas spécifier explicitement le type des paramètres des lambdas en utilisant simplement le mot clé var.

Par exemple,

Function<Integer, Integer> cube = (Integer x) -> x * x * x;

peut devenir : 

Function<Integer, Integer> cube = (var x) -> x * x * x;

Ceci peut représenter à la fois un gain de clarté, de concision et de temps lors de l’emploi de certains types à l’écriture assez lourde. Bien entendu, il n’était déjà pas obligatoire de spécifier un type pour les paramètres des lambdas, ainsi la déclaration : 

Function<Integer, Integer> cube = (x) -> x * x * x;

est tout à fait admissible.

A quoi sert donc l’inférence de type dans ce cas ? Elle est utile si l’on souhaite annoter nos paramètres, ce qui est impossible sans déclaration de type. En java 11, on pourra par exemple écrire des expressions telles que :

list.stream()
        .map((@NotNull var s) -> s.toUpperCase());

2. Lancement simplifié de programmes à fichier unique

Avant Java 11, pour lancer le traditionnel HelloWorld.java, on exécutait : 

javac -d <memory> HelloWorld.java
java -cp <memory> 

Désormais, avec Java 11, on pourra directement lancer des applications contenues dans un seul fichier via :

java HelloWorld.java

Cette fonctionnalité, en complément de JShell, complète les outils à dispositions des développeurs débutants et permet également à Oracle de faire évoluer Java vers un langage plus léger à exécuter. Cela pourrait également illustrer sa volonté de faire entrer Java dans la cours des languages de scripting dans le futur. Affaire à suivre ! 

3. Evolution de l’API http Client

Pour les utilisateurs de l’API HTTP Client introduite par Java 9 et mise à jour par Java 10, sachez que son implémentation a été presque entièrement revue par Java 11. Oracle assure que ces remaniements n’affectent pas l’API d’un point de vue haut-niveau, tout en améliorant un certain nombre de points techniques à la suite des retours de la communauté.

L’implémentation est maintenant complètement asynchrone. Le flux de données peut à présent être suivi plus facilement, depuis les requêtes utilisateurs jusqu’aux sockets. Cela devrait réduire considérablement la complexité du code et optimiser la possibilité de réutilisation entre HTTP / 1.1 et HTTP / 2.

Le nom du module et du package de l’API est désormais java.net.http.

4. Quelques APIs

Le JDK 11 embarque un certain nombre de nouvelles classeset méthodes intégrées à des modules déjà existants. La liste ci-dessous en estun aperçu non-exhaustif, mettant en lumière les ajouts me paraissant les plusimportants :

  • java.lang.String :
    • boolean isBlank(): retourne true si le String est vide ou n’est composé que de whitespaces, sinon false.
    • Stream lines(): retourne un stream des lignes extraites du String.
    • String repeat(int): retourne un String qui est le String d’origine concaténé avec lui-même n fois.
    • String strip(): retourne un String débarrassé de ses trailing et leading whitespaces. Pour faire simple, strip() est la version «Unicode-aware » de trim() dont la définition du whitespace date des premières versions de Java.
    • String stripLeading(): retourne un String débarrassé de ses leading whitespaces.
    • String stripTrailing(): retourne un String débarrassé de ses trailing whitespaces
  • java.util.function.Predicate
    • Predicate not(Predicate). Retourne un Predicate qui est la négation du Predicate passé en argument. Exemple pour filtrer une liste :
list.stream()
        .filter(Predicate.not(String::isBlank));
  • java.lang.CharSequence
    • int compare(CharSequence, CharSequence): compare deux instances de CharSequence en suivant l’ordre lexicographique. Retourne une valeur negative, nulle ou positive.
  • java.lang.StringBuffer / java.lang.StringBuilder
    • Ces deux classes ont désormais accès à une nouvelle méthode compareTo() qui prend en argument un StringBuffer/StringBuilder et retourne un int. La logique de comparaison suit le même ordre lexicographique que pour la nouvelle méthode de la classe CharSequence. o

5. Gestion de la visibilité des attributs des classes imbriquées

Java autorise la déclaration de plusieurs classes dans un seul fichier source, telles que les classes imbriquées (Nested Class). Du point de vue de l’utilisateur, elles sont toutefois généralement considérées comme appartenant à la « même classe ». Et, par conséquent, les utilisateurs s’attendent à ce qu’elles partagent un régime d’accès commun aux attributs ou méthodes de la classe mère. Pour préserver ces attentes, les compilateurs doivent élargir l’accès des attributs privés aux classes du même package en ajoutant des ponts d’accès. Une invocation d’un membre privé est compilée en un appel d’une méthode (getter) générée par le compilateur dans la classe cible, qui à son tour accède au membre privé prévu.

Par exemple, dans le cas d’une classe NestedClass, imbriquée à l’intérieur d’une classe NestingClass, qui a besoin d’accéder à un des attributs privés de la classe hôte :

public class NestingClass {
    private int nestingInt;

    class NestedClass {
        public void printNestingInt() {
            System.out.println("Nesting Int = " + nestingInt);
        }
    }
}

Le compilateur découple les deux classes et crée une méthode d’accès publique à nestingInt utilisée par la classe NestedClass :

public class CompiledNestingClass {
    private int nestingInt;

    public int proxyToGetNestingInt() {
        return nestingInt;
    }

}

class CompiledNestedClass {
    CompiledNestingClass nestingClass;

    public void printNestingInt() {
        System.out.println("Nesting Int = " + nestingClass.proxyToGetNestingInt());
    }
}

Ces ponts subvertissent l’encapsulation (private ne revêt plus exactement le même sens) et peuvent dérouter les utilisateurs et les outils. Une notion formelle d’un groupe de fichiers de classe formant un nid (ou nest), où les partenaires de nid partagent un mécanisme de contrôle d’accès commun, permet d’obtenir directement le résultat souhaité de manière plus simple, plus sécurisée et plus transparente.

Pour relier facilement classes imbriquées et hôtes, en Java 11 deux nouveaux attributs ont été ajoutés aux classes : NestHost (hôte du nid) et NestMembers (membres du nid).

On a aussi l’ajout de 3 méthodes à java.lang.Class :

  • Class getNestHost()
  • Class[] getNestMembers()
  • boolean isNestmateof(Class)

6. Dynamic Class-File Constants

Ce changement est une extension du format de fichier de classe permettant de prendre en charge un nouveau pool de constantes : CONSTANT_Dynamic.

Il semble contradictoire qu’une valeur soit à la fois constant et dynamique. Toutefois, vous pouvez essentiellement la considérer comme une valeur finale en Java. La valeur de la constante n’est pas définie au moment de la compilation (contrairement aux autres constantes), mais utilise une méthode de bootstrap pour déterminer la valeur au moment de l’exécution. La valeur est donc dynamique mais, comme sa valeur n’est définie qu’une fois, elle est également constante.Cette fonctionnalité introduit une nouvelle classe, java.lang.invoke.ConstantBootstraps, avec neuf nouvelles méthodes.

7. Le Garbage Collector Epsilon

Le garbage collector Epsilon est un peu inhabituel dans le sens où il ne collecte pas de déchets ! Il allouera de la mémoire, selon les besoins, lors de l’instanciation de nouveaux objets mais ne récupérera aucun espace occupé par des objets non référencés. Il s’agit d’une implémentation de GC « no-op » totalement passive, ouvertement expérimentale, qui est un changement isolé, ne touchant pas les autres GC et apportant des modifications minimes au reste de la JVM. Les cas d’utilisation de ce GC se résument pour l’instant à des tests de performance ou d’allocation mémoires ainsi qu’à des tâches à la durée de vie très courtes.

8. Unicode 10

Le JDK 11 a bien pris note de l’arrivée d’Unicode 10, et supporte donc la nouvelle version de ce standard. Cette version comprend 8 518 nouveaux symboles (du Bitcoin aux emojis en passant par des signes traditionnels chinois, arabes ou encore bouddhistes) que vous pourrez utiliser avec Java 11.

9. Suppresion des modules

Les modules Java EE et CORBA, d’abord, après avoir été dépréciés en Java SE 9, ont été retirés de Java SE Plateform et du JDK 11. Les modules affectés sont :

  • corba
  • transaction
  • activation
  • xml.bind
  • xml.ws
  • xml.ws.annotation

Ensuite, les modules JavaFX ont également été retirés du JDK 11. Ces modules faisaient partis des JDKs Oracle précédents, mais pas des livraisons des derniers OpenJDK. Cependant, pas de panique ! Ces modules sont toujours accessibles en dehors du JDK.

Bonus : De l’influence de l’âge et de la tradition impériale japonaise sur l’API java.time.chrono  

Et oui, la politique a des ramifications jusque dans les sanctuaires les plus sacrés y compris le langage Java ! En prenant la décision, pour des raisons de santé, de céder sa place d’Empereur du Japon à son fils Naruhito, l’actuel souverain Akihito mettra fin le 30 avril 2019 à l’ère Heisei. Une nouvelle ère s’ouvrira donc, pleine de promesses et d’espérance, mais également d’arrachages de cheveux de développeurs, puisque le nom de la future ère n’est pas encore connu. Ainsi, tout système informatique intégrant le calendrier japonais devra non-seulement être prêt à digérer le changement d’ère, mais également prévoir un mécanisme de mise à jour de son nom lorsque celui-ci sera connu.

Heureusement, Oracle a pensé à tout, puisque les packages java.time.chrono et java.util du JDK 11 supporteront ce changement d’ère. Aujourd’hui, un nom générique lui a été attribué (« 元号 » en japonais, « NewEra » dans les autres langues) et celui-ci sera remplacé par le vrai nom dans une prochaine mise à jour. Il est donc fortement conseillé de ne pas créer de dépendances vis-à-vis de ce placeholder.

Je transmets mes encouragements à celles et ceux qui subissent quotidiennement les impacts de ces changements géo-politico-calendaires, rendant leur travail ô combien plus pénible. En témoigne la gymnastique nécessaire pour récupérer le nom d’une ère japonaise, processus d’une simplicité exemplaire (ici le retour attendu en Java 11 est le String « NewEra ») :

java.time.chrono.JapaneseEra.of(3).getDisplayName(TextStyle.FULL, Locale.US)

ou plus limpide encore :

new java.util.Calendar.Builder()
    .setCalendarType("japanese")
    .setFields(Calendar.ERA, 5,
        Calendar.YEAR, 1,
        Calendar.MONTH, Calendar.MAY,
        Calendar.DAY_OF_MONTH, 1)
    .build()
    .getDisplayName(Calendar.ERA, Calendar.LONG, Locale.US)

Nous vous souhaitons une bonnes aventure avec Java 11 ! Dépêchez-vous d’en profiter, car Java 12 arrive très bientôt… dès mars 2019 !

Liens complémentaires