Java 22 : quoi de neuf ?

Java 22 : quoi de neuf ?

Maintenant que Java 22 est features complete (Rampdown Phase One au jour d’écriture de l’article), c’est le moment de faire le tour des fonctionnalités qu’apporte cette nouvelle version, à nous, les développeurs.

Cet article fait partie d’une suite d’article sur les nouveautés des dernières versions de Java, pour ceux qui voudraient les lire en voici les liens : Java 21, Java 20, Java 19, Java 18, Java 17, Java 16, Java 15, Java 14, Java 13, Java 12, Java 11Java 10, et Java 9.

JEP 461 – Stream Gatherers (Preview)

Enrichit l’API Stream avec le support d’opérations intermédiaires personnalisées. C’est une API en preview.

L’API Stream fournit un ensemble d’opération intermédiaire et terminale fixe. Elle permet d’étendre les opérations terminales via la méthode Stream::collect(Collector), mais ne permet pas d’étendre les opérations intermédiaires. Certaines opérations sont manquantes ou d’autre sont possible via un ensemble d’opération ou via une opération qui ne correspond pas totalement à ce dont on a besoin.

Au fil des ans, de nombreux ajouts d’opérations terminales ont été proposés, même si la plupart ont du sens, il n’est pas possible de tous les ajouter au SDK, ajouter la possibilité de définir ses propres opérations intermédiaires pallie au problème.

Avec la JEP 461, il est maintenant possible de définir ses propres opérations intermédiaires via Stream::gather(Gatherer).

Un gatherer représente la transformation d’un élément d’une Stream en one-to-one, one-to-many, many-to-one ou many-to-many et peut arrêter la transformation si nécessaire, arrêtant l’émission d’élément dans la stream.

On peut combiner les gatherers entre eux : stream.gather(...).gather(...).collect(...).

L’interface java.util.stream.Gatherer définit les méthodes suivantes :

  • initializer() : optionnel, permet de maintenir un état lors du traitement des éléments.
  • integrator() : intègre un nouvel élément depuis la stream entrante, et émet si nécessaire un élément dans la stream de sortie.
  • combiner() : optionnel, peut être utilisé pour évaluer le gatherer en parallèle pour les stream parallèle.
  • finisher() : optionnel, appelé quand la stream n’a plus d’élément en entrée.

L’API Stream a été enrichie avec les gatherer suivants :

  • fold : stateful many-to-one gatherer qui construit un agrégat de manière incrémentielle et émet cet agrégat lorsqu’il n’existe plus d’éléments d’entrée.
  • mapConcurrent : stateful one-to-one gatherer qui invoque de manière concurrente une fonction fournie pour chaque élément d’entrée, jusqu’à une limite fournie.
  • scan : stateful one-to-one gatherer qui applique une fonction fournie à l’état actuel et à l’élément actuel pour produire un élément en sortie.
  • windowFixed : stateful many-to-many gathere qui regroupe les éléments d’entrée dans des listes d’une taille fournie, émettant les fenêtres en sortie lorsqu’elles sont pleines.
  • windowSliding : stateful many-to-many gatherer qui regroupe les éléments d’entrée dans des listes d’une taille fournie. Après la première fenêtre, chaque fenêtre suivante est créée à partir d’une copie de son prédécesseur en supprimant le premier élément et en ajoutant l’élément suivant de la stream d’entrée.

Voici un exemple d’utilisation d’un gatherer fournit dans le JDK :

var numbers = List.of(1, 2, 3, 4, 5);

var slidingWindows = numbers.stream()
    .gather(Gatherers.windowSliding(3))
    .toList();

System.out.println(slidingWindows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

La JavaDoc donne l’exemple suivant de gatherer qui reproduit l’opération Stream.map() :

public <T, R> Gatherer<T, ?, R> map(Function<? super T, ? extends R> mapper) {
    return Gatherer.of(
        (unused, element, downstream) -> // integrator
            downstream.push(mapper.apply(element))
    );
}

Plus d’informations dans la JEP 461

JEP 458 – Launch Multi-File Source-Code Programs

Depuis Java 11, il est possible de lancer un programme depuis un fichier source .java sans étape de compilation préalable. Le launcher Java va alors compiler le programme en mémoire automatiquement avant son exécution.

Avec la JEP 458, il est maintenant possible de lancer un programme depuis un fichier source qui utilise une classe définie dans un autre fichier source, ce second fichier sera aussi compilé automatiquement en mémoire. Les fichiers source sont recherchés dans la hiérarchie de répertoire habituelle en Java qui reflète la structure des packages.

Seuls les fichiers source utilisés par le programme principal seront compilés en mémoire.

Plus d’informations dans la JEP 458

JEP 447 – Statements before super (preview)

Quand une classe étend une autre classe et veut appeler le constructeur de la classe parente dans son propre constructeur, la JVM oblige l’appel du constructeur parent à être la première instruction du constructeur de la classe parente. Ceci permet de s’assurer que tous les champs de la classe parente sont initialisés avant la construction de la classe enfant.

La JEP 447 est une fonctionnalité en preview qui autorise des instructions avant l’appel du constructeur parent tant que ceux-ci n’accèdent pas à l’instance en cours de création.

Plusieurs exemples sont donnés dans la JEP : validation des paramètres, pré-calculs d’arguments, …

Voici par exemple une validation de paramètre avant la JEP 447:

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        super(value);               // Potentially unnecessary work
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
    }

}

Et avec la JEP 447 :

public class PositiveBigInteger extends BigInteger {

    public PositiveBigInteger(long value) {
        if (value <= 0)
            throw new IllegalArgumentException("non-positive value");
        super(value);
    }

}

Le code est plus lisible et évite potentiellement des effets du constructeur parent.

Plus d’informations dans la JEP 447

457 – Class-File API (Preview)

La JEP 457 fournit une API standard pour parser, générer et transformer les fichiers de classe Java. Cette API est en preview.

L’écosystème Java a de nombreuses librairies qui permettent de parser et de générer des fichiers de classe Java: ASM, BCEL, Javassist, … La plupart des frameworks qui génèrent du bytecode les utilisent.

Le format des classe Java évolue tous les 6 mois, à chaque nouvelle release de Java, les framework de génération doivent donc évoluer en même temps au risque de ne pas supporter les dernières évolutions du langage.

Le JDK lui-même utilise ASM pour implémenter certain de ses outils ainsi que pour l’implémentation des lambda, ce qui crée une divergence entre les fonctionnalités d’une version de Java et ce qui peut être utilisé au sein de la JVM dans les portions nécessitant de la génération de fichier de classe, car il faut attendre une version d’ASM supportant les nouvelles fonctionnalités de la version N pour les utiliser dans la version N+1.

L’API Class-File vient pallier ce problème en fournissant au sein du JDK une API pour parser, générer et transformer des fichiers de classe.

La présentation suivante faite par Brian Goetz au VM Language Summit 2023 présente en détail l’API, sa conception et son utilisation : A Classfile API for the JDK.

Plus d’informations dans la JEP 457

ListFormat

ListFormat est un nouveau formateur qui permet de formater une liste de chaîne de caractère selon une locale selon le standard Unicode.

Exemple :

var list = List.of("Black", "White", "Red");
var formatter = ListFormat.getInstance();
System.out.println(formatter.format(list));
// [Black, White, Red]

Lors de la création du formateur on peut lui passer :

  • Une locale, sinon la locale par défaut sera utilisée.
  • Le type de l’énumération : STANDARD, OR ou UNIT. Par défaut STANDARD.
  • Le style de l’énumération : FULL, SHORT ou NARROW. Par défaut FULL.

La table suivante montre les différentes sorties pour la locale US :

FULL SHORT NARROW
STANDARD Black, White, and Red Black, White, & Red Black, White, Red
OR Black, White, or Red Black, White, or Red Black, White, or Red
UNIT Black, White, Red Black, White, Red Black White Red

Plus d’information dans l’issue JDK-8041488.

Les fonctionnalités qui sortent de preview

Les fonctionnalités suivantes sortent de preview (ou du module incubator) et passent en standard :

  • JEP 454Foreign Function & Memory API : API permettant l’interconnexion de la JVM avec du code natif.
  • JEP 456Unnamed Variables & Patterns : permet d’utiliser _ comme pattern ou variable anonyme (littéralement sans nom ou non-nommé).

Pour les détails sur celles-ci, vous pouvez vous référer à mes articles précédents.

Néanmoins, un changement important a été fait dans l’API Foreign Function & Memory qui doit être noté : l’introduction de la notion de méthode limitée (restricted). Certaines méthodes de cette nouvelle API sont marquées comme limitées : pour les utiliser, il faudra utiliser l’option ligne de commande --enable-native-access=module-name. Pour l’instant, l’accès à des méthodes limitées génère un warning, mais il se pourrait que leur accès soit interdit dans une future version de la JVM. Les méthodes limitées sont utilisées pour binder une fonction native et/ou une donnée native, ce qui est par nature unsafe. C’est pour cela que son accès doit être donné spécifiquement via une option ligne de commande.

Les fonctionnalités qui restent en preview

Les fonctionnalités suivantes restent en preview (ou en incubator module).

  • JEP-460Vector API : septième incubation, API permettant d’exprimer des calculs vectoriels qui se compilent au moment de l’exécution en instructions vectorielles pour les architectures CPU prises en charge. Cette nouvelle version inclut des bugfixes et des améliorations de performance.
  • JEP 464Scoped Values : seconde preview, permettent le partage de données immuables au sein et entre les threads. Pas de changement notable pour cette nouvelle preview.
  • JEP 462Structured Concurrency : seconde preview, nouvelle API permettant de simplifier l’écriture de code multi-threadé en permettant de traiter plusieurs tâches concurrentes comme une unité de traitement unique. Pas de changement notable pour cette nouvelle preview.
  • JEP 463Implicitly Declared Classes and Instance Main Methods : seconde preview, simplifie l’écriture de programmes simple en permettant de les définir dans une classe implicite (sans déclaration) et dans une méthode d’instance void main().
  • JEP 459String Templates : seconde preview, un string template est un littéral de String qui vous permet d’incorporer des expressions et des variables. Pas de changement notable pour cette nouvelle preview.

Pour les détails sur celles-ci, vous pouvez vous référer à mes articles précédents.

Divers

Divers ajouts au JDK :

  • Console.isTerminal() : retourne true si l’instance de la console est un terminal.
  • Class.forPrimitiveName(String) : retourne la classe associée au type primitif donné.
  • InetAddress.ofLiteral(String) : créé une InetAddress depuis la représentation textuelle de l’adresse IP. Cette méthode statique existe aussi pour les classes Inet4Address et Inet6Address.
  • RandomGenerator.equiDoubles(double left, double right, boolean isLeftIncluded, boolean isRightIncluded).

La totalité des nouvelles API du JDK 22 peuvent être trouvées dans The Java Version Almanac – New APIs in Java 22.

Des changements internes, de la performance, et de la sécurité

Le Garbage Collector G1 a vu une amélioration lors d’appel JNI (Java Native Interface) définissant une région critique, précédemment G1 était désactivé totalement, avec un risque de bloquer les threads de l’application nécessitant un GC, voir même d’out of memory.

Grâce à la JEP 423: Region Pinning for G1, G1GC est maintenant capable d’épingler (pinning) uniquement une région en cas de section critique JNI, évitant de bloquer les autres threads de l’application nécessitant un GC. Plus d’informations dans la JEP 423.

Le Parallel GC et le Serial GC ont aussi vu des amélioration au niveau du scan de la card table (la card table stocke les référence old-to-young). D’autres changements ont été faits côté Garbage Collector, vous pouvez les retrouver dans cet article de Thomas Schatzl : JDK 22 G1/Parallel/Serial GC changes.

Coté sécurité, de nouveaux certificats racines ont été ajoutés, et vous pouvez dorénavant utiliser java -XshowSettings:security pour voir les informations de configuration de la JVM lié à la sécurité. Vous pouvez vous référer à l’article de Sean Mullan pour une liste exhaustive des changements de sécurité inclus cette version : JDK 22 Security Enhancements.

JFR Events

Voici les nouveaux événements Java Flight Recorder (JFR) de la JVM :

  • CompilerQueueUtilization : pas de description.
  • NativeLibraryLoad : informations sur une bibliothèque dynamique ou une autre opération de chargement d’image native.
  • NativeLibraryUnload : informations sur une bibliothèque dynamique ou une autre opération de déchargement d’image native.
  • DeprecatedInvocation : invocation unique d’une méthode annotée avec @Deprecated.

Vous pouvez retrouver tous les événements JFR supportés dans cette version de Java sur la page JFR Events.

Conclusion

On aurait pu penser que Java 22 serait une version de stabilisation après la version 21 qui est LTS mais non, il y a un ajout majeur que sont les Stream Gatherer et quelques JEP autour de la simplification du langage et sa facilité d’utilisation. On peut noter aussi la sortie de preview de la Foreign Function & Memory API qui va permettre une utilisation simplifiée de fonctions natives en Java avec une API performante et plus facile d’utilisation que JNI.

Pour retrouver tous les changements de Java 22, se référer aux release notes.

2 réflexions sur « Java 22 : quoi de neuf ? »

  1. Merci pour ce chouette tour d’horizon.
    Petits typos à noter dans la section « ListFormat »: celons -> selon.
    Beau travail en tout cas, merci encore.

    Cordialement.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.