Depuis Java 9, les livraisons s’enchaînent tellement rapidement (tous les 6 mois) qu’il est difficile de suivre toutes les fonctionnalités apportées par chacune des versions… Mais pas d’inquiétude, j’ai rassemblé pour vous tout ce qui concerne Java 14 dans cet article !

Java 14 est sorti depuis le 17 mars 2020, et au programme, du lourd, du très gros matos: Switch expressions, Pattern matching, text blocks, records, NPEs plus claires, l’outil de packaging multiplateforme, et bien plus encore ! Pour chaque nouveauté, j’ai résumé ce qui me semblait le plus important mais vous aurez également le lien vers la JEP correspondante si vous voulez assouvir votre curiosité 😉

Prenez votre café, votre paire de lunettes de repos, et c’est parti !

Info de dernière minute: On me dit dans l’oreillette que IntelliJ propose maintenant de télécharger directement la JDK depuis l’éditeur ! Il est donc beaucoup plus simple de tester les dernières nouveautés et vous n’avez donc plus d’excuses pour faire de la veille techno 😜!

Switch expressions standardisées (JEP361)

Ça y est, après une Preview (Java 12) et une Second Preview (Java 13), il aura fallut 2 versions pour enfin standardiser les switch expressions.

Concrètement, il n’y a aucun changement de cette fonctionnalité entre la version 13 et 14 de Java (excepté le fait qu’il n’est plus nécessaire d’activer le mode preview).

Cependant, voici une piqûre de rappel:

Avant, on utilisait les switch expressions de manière très verbeuse, où le break perd de son sens et les cases sont répétés ligne par ligne :

var  weekPartType;
switch (day) {
    case MON:
    case TUE:
    case WED:
    case THU:
    case FRI:
         weekPartType = WORK_WEEK;
         break;
    case SAT:
    case SUN:
        weekPartType = WEEK_END;
        break;
    default:
        throw  new  UnsupportedOperationException("Unknown day: " + day);
}

Maintenant, on peut l’écrire comme les lambdas :

var  weekPartType = switch (day) {
    case MON, TUE, WED, THU, FRI -> WORK_WEEK;
    case SAT, SUN -> {
        yield WEEK_END;
    }
    default  ->  throw  new  UnsupportedOperationException("Unknown day: " + day);
}

Chaque bloc de code dans une switch expression doit impérativement terminer par un yield, qui vient donc replacer le break.

Quand la NullPointerException sort le grand jeu (JEP358)

On a tous vu un jour une NPE (NullPointerException) sortir de nulle-part, et compliquée à clarifier, avec le magnifique message complet comme il se doit: null !

Par exemple, avec une NPE déclenchée sur a.b().c().d() = 15, qui est null ? a ? b() ? c() ? Le seul moyen de détecter la source du null est d’analyser tout ça en mode debug…

Mais cette ère est révolue! En ajoutant le paramètre -XX:+ShowCodeDetailsInExceptionMessages à l’exécution, vous verrez votre erreur indiquer exactement la source du problème sous la forme Cannot invoke "a.b()" because "a" is null. Et c’est valable que ce soit pour des appels de méthodes, assignation de champs, tableaux, etc…

Avantage : Vous savez exactement d’où vient le problème.

Inconvénient : Le message d’erreur contiendra le nom des champs ou méthodes associées à l’erreur, ce qui pourrait potentiellement compromettre la sécurité de votre code car ces détails seront valables pour toutes vos NPE et non pour une classe précisément. Donc réfléchissez bien avant d’utiliser cette nouvelle fonctionnalité !

JFR Event Streaming, ou comment faire du live monitoring (JEP349)

En général, pour suivre la vie d’une application, soit on connecte un outil de monitoring, comme New Relic soit on profile l’application avec JProfiler ou VisualVM par exemple. . Dans le premier cas, on doit déterminer à l’avance les éléments qu’on veut monitorer et placer des sondes, dans le second, c’est un processus assez long qui nous donne des tendances mais pour lequel il est souvent difficile de reproduire les réelles conditions de production.

Java 14 est désormais accompagné du JDK Flight Recording Event Streaming qui permet d’avoir autant voire plus d’informations qu’un profilage, de manière continue et sans attendre la fin de la collecte !

Le seul problème étant la charge processeur qui peut s’avérer lourde quand il y a beaucoup d’événements observés.

Il nous reste maintenant plus qu’à attendre que les outils habituels de monitoring implémentent cette nouvelle API… Affaire à suivre !

En attendant, voici un article pour ceux souhaitant creuser un peu plus le sujet et mieux comprendre son utilisation (en anglais) : Java Flight Recorder and JFR Event Streaming in Java 14.

Les nouveautés en « beta-testing » de Java 14

Toutes les fonctionnalités en Preview et Second Preview ne seront accessibles que si vous activez le mode Preview, voici un article.

[Second preview] Les blocs de texte (JEP368)

Voici une autre fonctionnalité tant attendue qui fait rêver les devs comme moi aimant écrire des romans multilignes en String Java ou en SQL par exemple !

Les blocs de textes étaient déjà dans Java 13 en Preview, mais ont écopé d’un second tour afin d’apporter une modification qui a du sens : l’ajout des échappements \ et \s.

Mais bon, rien ne vaut une piqûre de rappel (déjà deux dans cet article !) :

Avant, pour faire une seule ligne en multiligne, on devait faire attention à l’espace à chaque fin de String concaténée :

@Query("SELECT e " +
    "FROM Employee e " +
    "WHERE e.organizationId = 42")
List<Employee> listEmployeesOnOrganization42();

Et maintenant, plus besoin de se préoccuper de la concaténation :

@Query("""
    SELECT e \
    FROM Employee e \
    WHERE e.organizationId = 42""")
List<Employee> listEmployeesOnOrganization42();

L’exemple donné montre un cas d’utilisation assez commun lorsqu’on fait des requêtes SQL avec les Spring repositories, d’où l’avantage du bloc de texte lorsqu’on a des requêtes complexes.

Un autre exemple avec des sauts de ligne…

Avant, il fallait échapper les caractères spéciaux (comme les sauts de ligne dans l’exemple) :

String  xmlConfig = "<root>\\n" +
                    "  <var1>value</var1>\\n" +
                    "  <var2>value</var2>\\n" +
                    "</root>";

Maintenant, les sauts de lignes sont implicites, et l’indentation est “devinée” en fonction de la ligne qui commence le plus à gauche :

String  xmlConfig = """
           <root>
               <var1>value</var1>
               <var2>value</var2>
           </root>""";

Explications et résumé:

  • Par défaut, chaque nouvelle ligne dans un bloc de texte indiquera un saut de ligne avec \n mais sans garder les espaces à la fin de chaque ligne (comme si la fin des lignes étaient “trim”)
  • Le caractère d’échappement \ en fin de ligne d’un bloc indique que la ligne ne finira pas par un saut de ligne (exactement le même fonctionnement qu’avec les lignes de commandes !) tout en conservant les espaces à la fin des lignes du bloc (exemple avec voici une ligne \ où les 3 espaces à la fin seront gardés)
  • Le caractère d’échappement \s en fin de ligne d’un bloc indique que la ligne finira par un espace et un saut de ligne tout en conservant les espaces à la fin des lignes du bloc (exemple avec voici•une•ligne••\s où les 2 espaces à la fin seront gardés, avec un 3ème espace et un saut de ligne. La String finale contiendra donc voici•une•ligne•••\n)
  • L’indentation à l’intérieur du bloc de texte sera “devinée” en fonction de la ligne qui commence le plus à gauche
  • Il n’est pas utile d’échapper les caractères spéciaux, comme les sauts de ligne \n ou les guillemets "

C’est pas beau la vie ?

[Preview] Le Pattern Matching: instanceof (JEP305)

Fonctionnalité simple mais puissante, elle permet de déclarer une variable locale directement dans la déclaration d’une condition d’instanceof, ce qui réduit le code et le clarifie.

Avant, nous avions besoin de déclarer la valeur castée dans une variable locale, assez verbeux :

if (obj instanceof String) {
    String  s = (String) obj;
    if (s.length() > 3) {
        // Utilisation de 's'
    }
}

Ou encore de caster directement dans la condition, ce qui rend le code beaucoup plus complexe à lire :

if (obj instanceof String && ((String)obj).length() > 3) {
    // Si on veut utiliser obj en tant que String, il faudra soit l'assigner, soit le caster à chaque fois...
}

Maintenant, nous pouvons directement caster et créer une variable locale correspondant au instanceof, tout ça en une seule ligne :

if (obj instanceof  String s && s.length() > 3) {
    // Utilisation de 's', on a gagné 2 lignes !
}

[Preview] Les records (JEP359)

Une nouvelle fonctionnalité qui va être bien utile pour éviter tout le code redondant (getters, setters, equals/hashcode/constructeur/toString…).

La puissance de cette nouveauté – ou plutôt de ce nouvel élément de langage – est qu’elle va faciliter grandement l’écriture des simples POJOs et nous permettra d’avoir un code plus clean avec une description de nos objets réduite à l’essentiel.

Allez, on commence par un exemple !

Avant, on avait:

class  EnginQuiRoule {
    private  final  boolean  enPanne;
    private  final  int  nbRoues;

    EnginQuiRoule(boolean  enPanne, int  nbRoues) {
        this.enPanne = enPanne;
        this.nbRoues = nbRoues;
    }

    boolean  isEnPanne() { return  this.enPanne; }
    int  getNbRoues(int  nbRoues) { return  this.nbRoues; }
    // toString, equals, hashcode...
}

static  void  main(String... args) {
    var  voitureEnPanne = new  EnginQuiRoule(true, 4);
    boolean  estEnPanne = voitureEnPanne.isEnPanne();
    int  nombreDeRoues = voitureEnPanne.getNbRoues();
}

Ensuite, on pouvait utiliser lombok pour nous éviter tout le code redondant:

@Value
class  EnginQuiRoule {
    boolean  enPanne;
    int  nbRoues;
}

static  void  main(String... args) {
    var  voitureEnPanne = new  EnginQuiRoule(true, 4);
    boolean  estEnPanne = voitureEnPanne.isEnPanne();
    int  nombreDeRoues = voitureEnPanne.getNbRoues();
}

Et maintenant avec les records, on a :

record EnginQuiRoule(boolean enPanne, int nbRoues) {}

static  void  main(String... args) {
    var  voitureEnPanne = new  EnginQuiRoule(true, 4);
    boolean  estEnPanne = voitureEnPanne.enPanne();
    int  nombreDeRoues = voitureEnPanne.nbRoues();
}

Un mini exemple avec de la prog réactive:

record LightResponse(String url, int responseStatus, String responseContent) {}
// ...
flux
    .flatMap(url ->  call(url).map(resp ->  new  LightResponse(url, resp.status, resp.content)))
    .filter(response ->  response.status() != 404)
    .map(LightResponse::responseContent)

Bien sûr, si vous souhaitez ajouter du comportement, il suffit de compléter le body du record, par exemple :

record LightResponse(Integer responseStatus, String responseContent) {
    public  LightResponse(Integer responseStatus, String responseContent) {
        if (responseStatus == null) throw  new  IllegalArgumentException("Pas bien !");
        // Inutile de faire this.url = url; car c'est implicite !
    }
}

Voici les quelques règles d’un record :

  • Les accesseurs générés sont en mode fluent programming, donc il n’y a donc pas de préfixe (en général get et is)
  • on peut implémenter une interface, mais pas étendre une classe.
  • toString() retourne bien chaque champ et leur valeur (exemple: MaClasse[champ1=X, champ2=Y])
  • le equals/hashCode est bien basé sur tous les champs

Personnellement, je vois cette fonctionnalité comme une révolution quand on fait de la programmation réactive avec Reactor, RxJava ou les Java Streams. En effet, lorsqu’on manipule nos objets dans un flux, on peut très facilement se retrouver avec un Tuple<String, Integer, String, Long, ObjX> dont il est difficile de connaître le contenu sans avoir lu tout le code ! 🤯 Avec les records, on pourra rapidement typer les objets et donner des noms explicites aux objets manipulés tout en ayant un code minimaliste.

Les autres nouveautés de Java 14, mais pas des moindres

Pour terminer, je vais lister ici d’autres nouveautés qui ne seront pas décrites, avec quelques liens si vous désirez approfondir le sujet :

  • NUMA-Aware Memory Allocation for G1 (JEP345) : Amélioration des performances du G1-GC sur les machines ayant beaucoup de mémoire en implémentant l’allocation de mémoire NUMA-aware (gestion de la mémoire à mi-chemin entre le SMP et le clustering).
  • Non-Volatile Mapped Byte Buffers (JEP352)
  • [Experimental] Portage de ZGC sur macOS (JEP364) et Windows (JEP365)
  • [Incubator] Foreign-Memory Access API (JEP370) : Actuellement nous avons l’Unsafe qui est très largement utilisé pour optimiser les accès mémoire, que ce soit pour de l’introspection ou du réseau. Le but de cette nouveauté serait d’optimiser l’actuel direct ByteBuffer et de se passer au maximum de l’Unsafe qui lui n’est pas une API officiellement supportée par Java, et qui peut facilement “casser” une application pour des problèmes de corruptions de mémoire.
  • [Incubator] Packaging Tool (JEP343) : Le nouvel outil permettant de générer un installeur CLI pour n’importe quel OS (deb/rpm pour Linux, pkg/dmg pour MacOS, et exe/msi pour Windows)
  • Suppression de l’outil historique Pack200 Tools and API (JEP367) : Utilisé il y a longtemps pour optimiser le transfert des jars. Si vous en avez encore besoin, vous pouvez vous tourner vers jlink
  • Suppression de Concurrent Mark Sweep (CMS) Garbage Collector (JEP363)

PS

Avec la sortie de Java 14 et de ces nombreuses nouveautés, utilisables avec ou sans le mode preview, la communauté Java nous affirme sa volonté de faire du langage Java un langage moderne et résolument tourné vers l’avenir.

Dans cet article, je vous ai parlé des nouveautés les plus importantes (à mes yeux). Cependant, si vous souhaitez aller plus loin, je vous conseille d’aller voir les release notes de Java 14 !

Pour toute question ou commentaire relatif à cet article, n’hésitez pas à laisser un message ci-dessous 😏 A bientôt !

Sources

Retrouvez toutes les nouveautés de Java et d’autres best practices sur notre blog !