Java 23 : quoi de neuf ?

Java 23 : quoi de neuf ?

Maintenant que Java 23 est features complete (Rampdown Phase Two 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 22, 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.

Tout d’abord, il y a eu un événement qui à ma connaissance ne s’était jamais passé dans une release de Java, une fonctionnalité en preview a été supprimée ! La fonctionnalité String Templates apparue en tant que preview feature en Java 21 a été supprimée, un re-design global de cette fonctionnalité sera fait, car elle avait soulevé de nombreux débats et ne semblait pas répondre aux attentes de la communauté.

La JEP 12 qui définit le processus des preview feature dit clairement qu’une feature en preview peut être supprimée à la discrétion du responsable de la fonctionnalité sans nécessité de nouvelle JEP.

Eventually, the JEP owner must decide the preview feature’s fate. If the decision is to remove the preview feature, then the owner must file an issue in JBS to remove the feature in the next JDK feature release; no new JEP is needed.

C’est donc ce qui a été fait ici.

Plus d’information sur la suppression des String Templates dans le ticket JDK-8329949.

JEP 455 – Primitive Types in Patterns, instanceof, and switch (Preview)

Fonctionnalité en preview qui ajoute le support des types primitifs dans les instanceof et les switch, et enrichit le pattern matching pour supporter des patterns de types primitifs : dans les instanceof, dans les cases des switch, et dans la déconstruction d’un record.

Les switchs supportent maintenant tous les types primitifs.

Exemple :

long l = ...;
switch (l) {
    case 1L              -> ...;
    case 2L              -> ...;
    case 10_000_000_000L -> ...;
    default -> ...;
}

On peut dorénavant utiliser instanceof pour tous les types primitifs.

Exemple tiré de la JEP :

if (i instanceof byte) {  // value of i fits in a byte
    ... (byte)i ...       // traditional cast required
}

Mais le plus intéressant est le support du pattern matching, voici des exemples pour chaque endroit où il est dorénavant possible de réaliser du pattern matching pour un type primitif.

Exemple de pattern matching d’un type primitif au sein d’un switch tiré de la JEP :

switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case 2 -> "error";
    case int i -> "unknown status: " + i;
}

Les guards sont aussi supportés via la clause when :

switch (x.getStatus()) {
    case 0 -> "okay";
    case 1 -> "warning";
    case int i when i > 1 && i < 100 -> "client error: " + i;
    case int i when i > 100 -> "server error: " + i;
}

Exemple de pattern matching d’un type primitif au sein d’un instanceof tiré de la JEP :

if (i instanceof byte b) {
    ... b ...
}

Exemple de pattern matching d’un type primitif lors de la déconstruction d’un record :

// JSON didn't differentiates integers to doubles, so a JSON number should be a double.
record JsonNumber(double number) implements JsonValue { }

var number = new JsonNumber(30);
// Previously we can only deconstruct a record via a its exact component type: double,
// now we can deconstruct and match a different priitive type
if (json instanceof JsonObject(int number)) {
    // ...
}

Cette évolution a nécessité d’implémenter des règles de conversion au sein du pattern matching, pour qu’un type primitif match un autre type primitif, comme dans l’exemple précédent 30 a matché un int même si le composant du record était défini en tant qu’un double, il faut que le type cible soit couvert par le test du pattern. Ici 30 est bien couvert par un int. Les valeurs non couvertes seront rejetées.

Plus d’information dans la JEP 455.

JEP 467 – Markdown Documentation Comments

Fonctionnalité qui permet d’écrire les commentaires de la documentation JavaDoc en Markdown et plus uniquement avec un mix de tag HTML et de tag JavaDoc.

Écrire du code HTML n’est pas toujours facile et très lisible sans un rendu, de plus, les tags JavaDoc sont parfois compliqués à utiliser. Markdown est un langage qui est à la fois lisible sans rendu, et simple d’utilisation. L’utiliser pour des commentaires JavaDoc est une super alternative. Markdown supporte l’utilisation de tag HTML offrant une grande flexibilité, les tags spécifiques JavaDoc sont toujours supportés si nécessaire.

Un commentaire en markdown commence par 3 slashs : ///.

Voici un exemple tiré de la JEP :

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * </li><li>If two objects are equal according to the {@link
 *     #equals(Object) equals} method, then calling the {@code
 *     hashCode} method on each of the two objects must produce the
 *     same integer result.
 * </li><li>It is $ly;em>not$lt;/em> required that if two objects are unequal
 *     according to the {@link #equals(Object) equals} method, then
 *     calling the {@code hashCode} method on each of the two objects
 *     must produce distinct integer results.  However, the programmer
 *     should be aware that producing distinct integer results for
 *     unequal objects may improve the performance of hash tables.
 * </li></ul>
 *
 * @implSpec
 * As far as is reasonably practical, the {@code hashCode} method defined
 * by class {@code Object} returns distinct integers for distinct objects.
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */

Qui serait écrit de la sorte en Markdown :

/// Returns a hash code value for the object. This method is
/// supported for the benefit of hash tables such as those provided by
/// [java.util.HashMap].
///
/// The general contract of `hashCode` is:
///
///   - Whenever it is invoked on the same object more than once during
///     an execution of a Java application, the `hashCode` method
///     must consistently return the same integer, provided no information
///     used in `equals` comparisons on the object is modified.
///     This integer need not remain consistent from one execution of an
///     application to another execution of the same application.
///   - If two objects are equal according to the
///     [equals][#equals(Object)] method, then calling the
///     `hashCode` method on each of the two objects must produce the
///     same integer result.
///   - It is _not_ required that if two objects are unequal
///     according to the [equals][#equals(Object)] method, then
///     calling the `hashCode` method on each of the two objects
///     must produce distinct integer results.  However, the programmer
///     should be aware that producing distinct integer results for
///     unequal objects may improve the performance of hash tables.
///
/// @implSpec
/// As far as is reasonably practical, the `hashCode` method defined
/// by class `Object` returns distinct integers for distinct objects.
///
/// @return  a hash code value for this object.
/// @see     java.lang.Object#equals(java.lang.Object)
/// @see     java.lang.System#identityHashCode

Plus d’information dans la JEP 467.

JEP 471 – Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

Unsafe est, comme son nom l’indique, une classe interne et non supportée du JDK qu’il n’est pas sans danger d’appeler. Pour des raisons historiques, de nombreux frameworks bas-niveau utilisaient Unsafe pour des accès mémoire plus rapide. Grâce aux fonctionnalités VarHandle API (JEP 193, depuis Java 9) et Foreign Function & Memory API (JEP 454, depuis Java 22), il y a maintenant des remplacements pour les méthodes d’Unsafe accédant à la mémoire qui sont aussi performantes mais plus sûres et supportées. Au total c’est plus de 79 méthodes sur les 87 que contient Unsafe qui sont concernées, permettant d’approcher le moment ou la classe en entière pourra être dépréciée puis supprimée !

Déprécier ces méthodes indique clairement qu’il est temps d’utiliser ces remplacements ! Néanmoins, la plupart d’entre nous ne devraient pas voir ces changements, car Unsafe est rarement utilisé autre part que dans des frameworks ou des librairies.

Ces méthodes seront dépréciées puis dégradées progressivement :

  • Phase 1 : dépréciation en Java 23 (cette JEP)
  • Phase 2 : log un warning au runtime si utilisées, en Java 24 ou 25
  • Phase 3 : génère une exception par défaut (comportement modifiable via une option de ligne de commande), en Java 26 ou supérieur
  • Phases 4 et 5 : suppression des méthodes après Java 26 (méthodes pour accès mémoire on-heap, puis off-heap)

Plus d’information dans la JEP 471.

JEP 474 – ZGC: Generational Mode by Default

ZGC est un Garbage Collector créé pour supporter des heaps de tailles très importantes (plusieurs téraoctets) avec des pauses très faibles (de l’ordre de la milliseconde).

L’ajout d’une heap générationnelle en Java 21 via la JEP 439 lui permet de supporter des workloads différents en consommant moins de ressources.

Le mode générationnel est maintenant le défaut.

Plus d’information dans la JEP 474.

JEP 476 – Module Import Declarations (Preview)

En Java, il est possible d’importer :

  • Toutes les classes d’un package avec l’instruction import java.util.*;
  • Une classe avec l’instruction import java.util.Map;
  • Toutes les méthodes et variables statiques d’une classe avec l’instruction import static org.junit.jupiter.api.Assertions.*;
  • Une méthode ou une variable statique avec l’instruction import static org.junit.jupiter.api.Assertions.assertTrue;

Mais il n’était pas possible d’importer en une seule instruction toutes les classes d’un module, c’est chose faite via l’instruction import module java.base; qui va en une instruction importer toutes les classes de tous les packages exportés du module java.base ainsi que ceux des modules requis transitivement par java.base.

Plus d’information dans la JEP 476.

Les fonctionnalités qui sortent de preview

Aucune fonctionnalité précédemment en preview (ou en incubator module) n’est sortie de preview ou d’incubation en Java 23.

À part bien sûr la fonctionnalité String Templates dont j’ai parlé en introduction qui est sortie de preview pour aller nulle part, car supprimée.

Les fonctionnalités qui restent en preview

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

  • JEP 466Class-File API : seconde preview, API standard pour parser, générer et transformer les fichiers de classe Java. Améliorations basées sur les retours d’utilisation. On peut noter que la migration du JDK vers cette nouvelle API a continuée en Java 23.
  • JEP 469Vector API : huitiè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. Aucun changement, il a été acté dans la JEP que la Vector API sera en incubation tant que les fonctionnalités du projet Valhalla ne seront pas disponibles en preview. Ce qui était attendu, car la Vector API pourra alors tirer parti des améliorations de performance et de représentation en mémoire que devrait apporter le projet Valhalla.
  • JEP 473Stream Gatherers : seconde preview, Enrichit l’API Stream avec le support d’opérations intermédiaires personnalisées. Aucun changement.
  • JEP 477Implicitly Declared Classes and Instance Main Methods : troisième 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(). Deux changements : les classes implicites importent automatiquement les 3 méthodes statiques print(Object), println(Object) et readln(Object) de la nouvelle classe java.io.IO, et elles importent automatiquement, à la demande, les classes des packages du module java.base.
  • JEP 480Structured Concurrency : troisième 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. Aucun changement.
  • JEP 481Scoped Values : troisième preview, permettent le partage de données immuables au sein et entre les threads. Un changement mineur.
  • JEP 482Flexible Constructor Bodies : seconde preview, fonctionnalité qui autorise des instructions avant l’appel du constructeur parent tant que celles-ci n’accèdent pas à l’instance en cours de création. Un constructeur peut maintenant initialiser les champs de la même classe avant d’appeler explicitement un constructeur.

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

Divers

Divers ajouts au JDK :

  • Suite à la JEP 477Implicitly Declared Classes and Instance Main Methods, en plus de la nouvelle classe IO, les mêmes 3 méthodes ont été ajoutées à la classe Console : print(Object), println(Object) et readln(Object).
  • La classe Console a vu l’ajout de 3 nouvelles méthodes permettant l’utilisation de string formatées avec une locale : format(Locale, String, Object), printf(Locale, String, Object) et readLine(Locale, String, Object).
  • Console.readPassword(Locale, String, Object) : identique à Console.readPassword(String, Object) mais en prenant une locale en paramètre pour la localisation de la chaîne de caractère.
  • Inet4Address.ofPosixLiteral(String) : crée une Inet4Address basée sur la représentation textuelle fournie d’une adresse IPv4 sous une forme compatible POSIX inet_addr.
  • java.text.NumberFormat et ses descendants ont vu l’ajout des méthodes setStrict(boolean) et isStrict() qui permet de changer le mode de formatage, le mode par défaut est strict.
  • Instant.until(Instant) : calcule la Duration jusqu’à un autre Instant.

Les méthodes suivantes ont été supprimées, elles avaient été dépréciées pour suppression, et dégradées pour jeter une exception dans une release précédente :

  • Thread.resume() et Thread.suspend().
  • ThreadGroup.resume(), ThreadGroup.stop() et ThreadGroup.suspend().

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

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

Le garbage collector Parallel GC a vu une ré-implémentation de son algorithme Full GC pour utiliser un algorithme plus classique de type parallel Mark-Sweep-Compact. C’est le même qu’utilisé par le garbage collector G1 et il permet d’optimiser les performances dans certains cas spécifiques et réduit l’utilisation de la heap de 1.5% lors de l’utilisation du Paralle GC. D’autres changements ont été faits côté Garbage Collector, vous pouvez les retrouver dans cet article de Thomas Schatzl : JDK 23 G1/Parallel/Serial GC changes.

Je n’ai pas noté d’autres changements notables pour l’instant, mais je mettrais cet article à jour si j’en découvre d’autres.

JFR Events

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

  • NativeMemoryUsageTotalPeak : Total des pics d’utilisation de la mémoire native pour la JVM (GraalVM Native Image uniquement). Peut ne pas être la somme exacte des événements NativeMemoryUsagePeak en raison du timing.
  • NativeMemoryUsagePeak : Utilisation maximale de la mémoire native pour un type de mémoire donné dans la JVM (GraalVM Native Image uniquement).
  • SerializationMisdeclaration : Méthodes et champs mal déclarés. Les contrôles sont généralement effectués une seule fois par classe sérialisable, la première fois qu’elle est utilisée par la sérialisation. En cas de forte pression sur la mémoire, une classe peut être vérifiée à nouveau.
  • SwapSpace : espace swap OS

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

Conclusion

Cette nouvelle version de Java est plutôt chiche en nouvelles fonctionnalités, et peu de fonctionnalités en cours de développement sont sorties de preview.

Le support des types primitifs dans le pattern matching ainsi que le support du Markdown dans la JavaDoc sont des améliorations néanmoins fort intéressantes, mais la disparition des String Templates sans remplaçant fait augurer d’un support de cette fonctionnalité très attendue assez éloigné dans le temps.

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

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.