JDK 16 Nouveautés

Contexte

La version 16 du JDK va être disponible cette semaine. Nous avons eu l’occasion de voir à travers différents billets un grand nombre de ces fonctionnalités. C’est l’occasion de faire une synthèse sur l’ensemble des nouveautés.

La prochaine version sera la future version LTS (Long Term Support) avec la sortie du JDK 17 en septembre 2021

Nouvelles fonctionnalités

394 - Pattern Matching for instanceof

L’objectif est de pouvoir écrire le code suivant :

if (obj instanceof String s) {
    // utilisation de s en tant que chaine de caractere
    ... s.contains(..);
} else {
    // pas possible d'utiliser la variable s
}

Dans le premier bloc (if), nous pouvons utiliser directement la variable s comme une chaîne de caractère. D’un autre côté, dans le second bloc (else), la variable s n’est pas connue.

Son utilisation est aussi possible dans l’évaluation des conditions. Cela donne une écriture élégante :

if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}

avertissementPlus d’informations sur le chapitre Pattern Matching for instanceof du billet du JDK15.

395 - Records

Avec les enregistrements ("records"), nous pouvons écrire simplement :

record Point(int x, int y) { }

astuce Vous voyez l’introduction d’un nouveau mot clé : record.

De ce fait, nous avons automatiquement :

  • un accesseur public pour chaque composant

  • un attribut final privé pour chaque composant

  • un constructeur contenant chaque composant

  • une méthode equals()

  • une méthode hashcode()

  • une méthode toString() pour visualiser les valeurs de chaque composant.

Des contrôles peuvent être faites lors de la construction:

record Range(int lo, int hi) {
    Range {
        if (lo > hi)  // référence implicite aux paramètres du constructeur
            throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
    }
}

avertissementPlus d’informations sur le chapitre Records du billet du JDK15.

396 - Stringly Encapsulate JDK Internals by Defaut

Lors de la mise en place de la modularité du JDK pendant le JDK 9, il a été prévu de contrôler les accès aux API internes. L’objectif est qu’un programme Java ne puisse pas y avoir accès. De ce fait, le JDK devient plus facile à maintenir et à le faire évoluer. Bien que cela soit déconseillé par Sun à l’époque, les pratiques ont fait que les programmes et notamment certaines librairies en avaient besoin.

En parallèle, un effort a été fait pour fournir des nouvelles API pour éviter l’usage de ces API internes.

Pour contrôler l’accès à cette API, il existe une option de la JVM depuis le JDK 9.

  • --illegal-access=permit autorise les accès illégaux en affichant un avertissement lors du premier accès illégal du package.

  • --illegal-access=warn identique à permit mais l’avertissement apparait à chaque appel.

  • --illegal-access=debug identique à warn mais l’avertissement est accompagné d’une pile d’appel.

  • --illegal-access=deny interdit tous les accès illégaux.

Du JDK 9 à 15, le mode par défaut est permit.

La nouveauté est qu’à partir du JDK 16, le mode par défaut est deny.

L’option est toujours disponible.

avertissementDonc, si besoin, nous pouvons autoriser l’accès illégaux avec le mode permit. Mais cela consiste à mieux reculer pour se prendre le mur.

avertissementPlus d’informations sur le sur le billet dédié.

388 - Unix-Domain Socket Channels

L’objectif est de pouvoir utiliser des sockets Unix. Pour cela, nous avons une nouvelle famille de protocole StandardProtocolFamily.UNIX et nous avons une classe qui représente l’adresse correspondante UnixDomainSocketAddress. Nous la créons à partir d’un simple fichier.

Côté serveur, nous avons le code suivant :

ServerSocketChannel ssc = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
ssc.bind(UnixDomainSocketAddress.of("/tmp/mon-sock"));
while ( true ) {
  SocketChannel sc = ssc.accept();
  // Utilisation du SocketChannel sc
  ...
}

Coté client, nous avons le code suivant :

SocketChannel sc = SocketChannel.open(StandardProtocolFamily.UNIX);
sc.connect(UnixDomainSocketAddress.of("/tmp/mon-sock"));
// Utilisation du SocketChannel sc
...

avertissementSeul, l’API ByteBuffer est disponible.

avertissementPlus d’informations sur le sur le billet dédié.

390 - Warnings for Value-Based Classes

L’objectif est de préparer l’introduction des "Value based classes" au sein du JDK. Ces classes ont les propriétés suivantes :

  • sont des classes finales et immutables,

  • possèdent les méthodes : equals(), hascode() et toString(),

  • l’égalité en utilisant la méthode equals() et non l’opération d’égalité (==),

  • n’ont pas de constructeurs mais une méthode fabrique "factory method" qui n’apporte aucun garantie sur l’identité.

astuce L’objectif est de mieux gérer les "inlines types", notamment avec les types primitifs. Cela devrait permettre d’augmenter les performances.

Des candidats ont été identifiés. Nous avons par exemple :

  • les classes "wrapper" des types primitives : java.lang (Byte, Short, Integer, Long, Float, Double, Boolean et Character)

  • les classes optionnelles : java.util (Optional, OptionalInt, OptionalLong et OptionalDouble)

  • certaines classes de l’API java.time (Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId, OffsetTime, OffsetDateTime, ZoneOffset, Duration, Period, Year, YearMonth et MonthDay)

avertissementPlus d’informations sur le sur le billet dédié.

Nouvelles fonctionnalités (Côté portage)

386 - Alpine Linux Port

Cela consiste plus exactement à réaliser un JDK pour les systèmes utilisant muslc comme librairie C.

avertissementPlus d’informations sur le sur le billet dédié.

388 - Windows/AArch64 Port

AArch64 correspond aux processeurs basés sur l’architecture ARM 64bits.

avertissementPlus d’informations sur le sur le billet dédié.

Fonctionnalités en mode Aperçu

397 - Sealed Classes (Second Preview)

Actuellement, pour le contrôle de l’héritage, nous avons uniquement le mot clé final. Les classes scellés est un moyen pour le développeur d’avoir plus de contrôle. Pour cela, nous avons deux nouveaux mot clés sealed et non-sealed. Nous avons trois mot clés :

  • final : permet de bloquer l’héritage. Il ne peut pas avoir des classes dérivées.

  • sealed : permet d’avoir des classes dérivées à condition qu’elles fassent partir des classes autorisées pour cela il y a le mot clé permits à utiliser.

  • non-sealed : permet de ne pas limiter les classes dérivées.

Par exemple, nous avons :

package com.exemple.geometrie;

public abstract sealed class Forme
    permits Cercle, Rectangle, Carre {
	...
}

Fonctionnalités en mode Incubation

333 - Vector API (Incubator)

Certains processeurs incluent des intructions pour les calculs vectoriels. Le but est de fournir une API Java afin de réaliser ce type de calcul. Ainsi, à l’exécution, la JVM peut réaliser des optimisations en fonction du matériel présent.

Les objectifs sont :

  • Clair et concis : L’API doit permet de préciser des grands calculs vectoriels.

  • Indépendant du matériel : Ne pas être spécifique à un matériel donné.

  • Fonctionne sous x64 et AArch64

  • Dégradation possible : Si le matériel ne prend pas en charge ce type de calcul, la JVM prendra le relais pour la réalisation des opérations.

389 - Foreign Linker API (Incubator)

Le but est de fournir une API Java pour accèder directement à une librairie native.

Les objectifs sont les suivants :

  • Facile à utiliser : Etre plus facile que JNI (Java Native Interface)

  • Support C : Assurer la compatibilité avec les librairies C sur x64 et AArch64

  • Généralité : Flexible pour supporter d’autres plateformes comme 32-bit x86 ou d’autres langages (C++, Fortan)

  • Performance : Comparable à JNI, voire meilleur.

Voici un exemple d’appel de la méthode strlen

// Recherche de la méthode stren
MethodHandle strlen = CLinker.getInstance().downcallHandle(
        LibraryLookup.ofDefault().lookup("strlen"),
        MethodType.methodType(long.class, MemoryAddress.class),
        FunctionDescriptor.of(C_LONG, C_POINTER)
    );

// Appel de la méthode strlen
try (MemorySegment str = CLinker.toCString("Hello")) {
   long len = strlen.invokeExact(str.address()); // 5
}

avertissementCette fonctionnalité s’appuie sur la fonctionnalité suivante pour l’allocation mémoire.

393 - Foreign Memory API (Third Incubator)

L’objectif de cette API est de fournir un moyen afin qu’un programme Java puisse allouer de la mémoire externe, en dehors du tas ("heap").

Cette troisième version permet le raffinement suivant :

  • Séparation clair entre les interfaces MemorySegment et MemoryAddress

  • Nouvelle classe MemoryAccess qui fournit un ensemble de méthodes statiques pout les usages commun

  • Support de la mémoire partagée

  • Capacité d’enregistrer des segments avec des nettoyeurs

Voici un exemple de code :

try (MemorySegment segment = MemorySegment.allocateNative(10 * 4)) {
    for (int i = 0 ; i < 10 ; i++) {
       MemoryAccess.setIntAtIndex(segment, i);
    }
}

avertissementPlus d’informations sur le Foreign Memory Access API du billet du JDK15.

Fonctionnalité sour le capot

347 - Enable C++14 Language Features

avertissementCela concerne les contributeurs du JDK.

Cela reste quand même intéressant de se rappeler qu’une partie du JDK est développée en Cplusplus.

Jusqu’à présent, les contributeurs étaient limités à Cplusplus 98/03. Cette JEP permet d’autoriser les fonctionnalités du standard Cplusplus 14. Il fournit aussi les règles d’utilisation pour HotSpot.

Cela permet aussi de pouvoir utiliser des compilateurs récents.

376 - ZGC:Concurrent Thread-Stack Processing

L’objectif est de déplacer l’opération "thread-stack processing" du point de restauration à une phase concurrente.

Cela devrait améliorer les performances car le temps pour réaliser le point de restauration sera très court (< 1 ms).

387 - Elastic Metaspace

Depuis le JDK 8, la mémoire permanente a été supprimée (Rappelez vous des anciennes options : PermSize et MaxPermSize). (cf JEP 122 - Remove the Permanent Generation). Cela a été remplacé par un nouveau espace : le Metaspace.

Dans la plupart des cas, cela fonctionne très bien. Cependant, des cas existent où la mémoire non utilisée est gaspillée de façon excessive.

L’objectif est de réduire l’empreinte mémoire du Metaspace, simplifier de code et de pouvoir libérer la mémoire non utilisée.

Outilllage

357 - Migrate from Mercurial to Git

L’objectif est l’abandon de Mercurial pour passer à Git comme gestionnaire de sources.

astuce Bien que cela concerne les contributeurs du JDK, cela permet à chaque développeur de rentrer plus facilement dans le code du JDK et de comprendre les évolutions.

avertissementPlus d’informations sur le sur le billet dédié.

369 - Migrate to Github

Suite au choix de Git, l’objectif est de réaliser la migration vers un fournisseur. C’est Github qui a été retenu.

Cependant un travail a été fait pour rester générique avec une url neutre https://git.openjdk.java.net/jdk

avertissementPlus d’informations sur le sur le billet dédié.

392 - Packaging Tool

L’objectif est de fournir un moyen d’installation des applications Java. A l’instar des anciennes fonctionnalités comme Java Web Start (supprimée en JDK 11) et pack200 (supprimée en JDK 14), l’idée est de s’appuyer sur la création de packages natifs.

  • Linux : deb et rpm

  • macOS : pkg et dmg

  • Windos : msi et exe

Il est à noter qu’il n’y a pas d’interface graphique pour l’outil. C’est exclusivement en ligne de commande.

Cas d’usage pour une application non modulaire

jpackage --name myapp --input lib --main-jar main.jar

Cas d’usage pour une application modulaire

jpackage --name myapp --module-path lib -m myapp

avertissementL’outil n’est pas cross-platforme. C’est à dire que pour réaliser un programme d’installation pour Windows, l’outil jpackage doit être exécuté sous Windows. Et cela, pour l’ensemble de plate-formes.