Tasse de café fumante symbolisant le logo java 23 posée sur une table en bois

Java 23 : quelles sont les nouveautés ?

Java 23 est officiellement disponible depuis le 17 septembre 2024. Cette version fait suite à la LTS Java 21, et précède la prochaine LTS attendue, Java 25, prévue pour septembre 2025.
Explorez les nouvelles features de Java 23, ainsi que les fonctionnalités en preview ayant évolué et les fonctionnalités désormais dépréciées.

#01

Java 23 : Fonctionnalité disponible dès à présent

JEP 467: Markdown Documentation Comments

Jusqu’à présent, pour formater des commentaires JavaDoc, il était nécessaire d’utiliser HTML. Or, aujourd’hui, Markdown est devenu plus populaire pour rédiger de la documentation. Cette JEP apporte la possibilité d’écrire des commentaires JavaDoc avec un formatage Markdown.

L’exemple ci-dessous présente la documentation de la méthode java.lang.Object.hashCode.

/**
* 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>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>It is <em>not</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.
* </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
*/

Cette même documentation a la possibilité d’être écrite en Markdown. Vous trouverez ci-dessous un aperçu de la documentation de la méthode hashCode formatée 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

Nous notons plusieurs différences :

  • L’utilisation de Markdown est indiquée par un nouveau format de commentaire de documentation : les lignes commencent par /// à la place de la syntaxe traditionnelle /** … */
  • À la place de {@code … }, le code source est marqué par
`...`
  • Les liens, noté {@link … } en Javadoc HTML, sont désormais noté par [… ]
  • Le tag HTML <p> a été remplacé par une ligne vide
  • Les tags d’énumérations <ul> et <li> sont remplacés par la liste à puces Markdown
  • Les tags de détails spécifiques à la JavaDoc, comme @implSpec, @return, et @see restent inchangés

Vous trouverez ci-dessous un comparatif détaillé.

 

comparatif codes java 23

#02

Java 23 : features en Preview ou Incubation

Actuellement en phase de développement, ces fonctionnalités ne sont pas disponibles par défaut et nécessitent une activation manuelle. Elles seront modifiées ou supprimées dans une version ultérieure.
 

JEP 476: Module Import Declarations (Preview)

Depuis la version 1.0, le compilateur Java importe implicitement java.lang dans chaque fichier source Java. Ainsi, les classes telles que Object, Integer ou Exception n’ont pas besoin d’être importées manuellement.

Nous bénéficions également de la possibilité d’importer manuellement des packages entiers tels que java.util.* avec import java.util.*.

Grâce à cette JEP, nous sommes en mesure d’importer toutes les classes des packages exportées par un module entier via import module M, simplifiant ainsi la gestion des imports en réduisant le nombre de lignes nécessaires dans de nombreux scénarios.

Prenons un exemple avec le code suivant, qui requiert 4 lignes d’imports :

import java.util.Map; // or import java.util.*;
import java.util.function.Function; // or import java.util.function.*;
import java.util.stream.Collectors; // or import java.util.stream.*;
import java.util.stream.Stream; // (can be removed)

String[] fruits = new String[] { "apple", "berry", "citrus" };
Map<String, String> m =
  Stream.of(fruits)
    .collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
                              Function.identity()));

En utilisant l’import du module java.util, le code devient :

import module java.util;

String[] fruits = new String[] { "apple", "berry", "citrus" };
Map<String, String> m =
  Stream.of(fruits)
    .collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
                              Function.identity()));

Nom de classe ambigu

Si deux classes du même nom sont importées, le compilateur lance une erreur.

import module java.base; // importe java.util, qui possède une classe publique Date
import module java.sql; // importe java.sql, qui possède une classe publique Date

. . .
Date date = ... // Compiler error: "reference to Date is ambiguous"
. . .

 

Il suffit alors de préciser quelle classe est choisie via son import direct :

import module java.base;
import module java.sql;

import java.sql.Date; // Résout l'ambiguïté de la classe Date utilisée !

...
Date d = ...

...

 

Transitivité de l’import

Si un module importé importe transitivement d’autres modules, les classes de ces autres modules seront aussi accessibles implicitement.

Par exemple, le module java.sql requiert transitivement le module java.xml :

module java.sql {
  . . .
  requires transitive java.xml;
  . . .
}

 

En important le module java.sql, vous pouvez accéder directement aux classes du module java.xml :

import module java.sql;

. . .
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
. . .

 

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

Depuis la version Java 16, instanceof offre, d’une part, la vérification du type d’une variable, mais réalise aussi un cast implicite :

if (obj instanceof String s && s.length() >= 5) {
  System.out.println(s.toUpperCase());
} 
else if (obj instanceof Integer i) {
  System.out.println(i * i);
} 
else {
  System.out.println(obj);
}

 

De plus, Java 21 a introduit la possibilité de vérifier directement le type d’une variable avec un switch :

switch (obj) {
  case String s when s.length() >= 5 -> System.out
                                          .println(s.toUpperCase());
  case Integer i -> System.out.println(i * i);
  case null, default -> System.out.println(obj);
}

 

Jusqu’à présent, instanceof et switch ne fonctionnaient qu’avec des types non primitifs. Grâce à la JEP 455 de Java 23, nous sommes à présent en capacité d’appliquer ces vérifications aux types primitifs.

Lorsque nous utilisons des types primitifs pour vérifier un type via switch ou instanceof, la sémantique diffère légèrement des types non primitifs. En ne testant qu’une variable de type primitif X est d’un certain type primitif P, nous évaluons en réalité si la valeur de la variable testée est en mesure d’être stockée dans le type P. Illustrons par un exemple :

int x = 65;
if (x instanceof char c) {
System.out.println("c = " + c); // Sortie : "c = A"
}

Dans cet exemple, nous utilisons instanceof pour vérifier que le contenu de la variable peut être stockée dans un type primitif char, casté vers la variable c. Le résultat est 'A' car ce caractère correspond au code ASCII 65.

Types primitifs dans un switch

En utilisant un switch, vous avez la possibilité de vérifier de manière concise si un type primitif est en mesure d’être stocké dans un autre, tout en exploitant la valeur résultante du cast.

Voyons ci-dessous un exemple avec un float de valeur 100000.0 :

float value = 100000.0F;
switch (value) {
  case byte b -> System.out.println(value + " instanceof byte: " + b);
  case short s -> System.out.println(value + " instanceof short: " + s);
  case char c -> System.out.println(value + " instanceof char: " + c);
  case int i -> System.out.println(value + " instanceof int: " + i);
  case long l -> System.out.println(value + " instanceof long: " + l);
  case float f -> System.out.println(value + " instanceof float: " + f);
  case double d -> System.out.println(value + 
                                      " instanceof double: " + d);
}

 

Sortie :

100000.0 instanceof int: 100000
100000.0 instanceof long: 100000
100000.0 instanceof float: 100000.0
100000.0 instanceof double: 100000.0

Nous obersvons que seuls les types int, long, float et double contiennent la valeur float 100000.0. En effet, la portée des valeurs des types byte, short et char, limitée à respectivement 127, 32767 et 65535, est trop restreinte pour la valeur 100000.

Nous notons aussi les principes suivants :

  • Types dominants et types dominés : un type dominant est un type capable de stocker toutes les valeurs d’un type dominé.
    • Exemple : le type byte est dominé par le type int, car un int est capable stocker toutes les valeurs d’un byte
  • Exhaustivité du switch : un switch couvre toutes les valeurs possibles d’une valeur en entrée.
    • Exemple :
long value = ...
switch (value) {
  case byte  b -> System.out.println(value + " instanceof byte");
  case short s -> System.out.println(value + " instanceof short");
}
// Erreur du compilateur : le switch ne couvre pas toutes les possibilités de la valeur en entrée.

 

int value = ...
switch (value) {
  case byte  b -> System.out.println(value + " instanceof byte");
  case short s -> System.out.println(value + " instanceof short");
  case long  l -> System.out.println(value + " instanceof long");
}
// Compilateur OK

 

JEP 473: Stream Gatherers (Second Preview)

L’API Stream introduite depuis Java 8 offre la possibilité d’effectuer des opérations de manière déclarative sur une séquence d’éléments.

Trois étapes composent un Stream :

  1. La création à partir d’une Collection, d’un tableau ou en utilisant la méthode Stream.of().
  2. Les opérations intermédiaires qui transforment le Stream (exemples : map, filter, limit).
  3. Les opérations terminales qui finissent le Stream (exemples : collect, reduce, forEach).

L’API Stream existante offre un ensemble défini d’opérations intermédiaires et terminales et propose d’étendre les opérations terminales via la méthode Stream::collect(Collector). Cependant, elle n’offre pas la possibilité d’étendre les opérations intermédiaires.

Les Streams Gatherers introduisent désormais la possibilité de définir ses propres opérations intermédiaires grâce à la méthode Stream::gather(Gatherer). De plus, la classe java.util.stream.Gatherers met à disposition plusieurs Gatherers prédéfinis, tels que fold, mapConcurrent et windowFixed.

Cette JEP, initialement introduite dans Java 22 par la JEP 461,  est de nouveau disponible dans Java 23, sans modification par rapport la version précédente.

 

JEP 482: Flexible Constructor Bodies (Second Preview)

Il s’agit de la deuxième preview pour une JEP qui a été présentée dans Java 22 sous la JEP 447. Grâce à cette JEP, une classe dérivée inclue désormais des instructions dans son constructeur avant d’appeler la méthode super() de la classe parente.

Avant :

public class PositiveBigInteger extends BigInteger {

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

 

Avec la JEP 447 :

public class PositiveBigInteger extends BigInteger {

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

 

Cependant, cette JEP ne propose pas d’initialiser des attributs avant l’appel à super(). Cette nouvelle preview introduit à présent cette fonctionnalité.

public class PositiveBigInteger extends BigInteger {
  private final long max;

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

 

JEP 466: Class-File API (Second Preview)

Nous bénéficions d’une API standard pour analyser, générer et transformer des fichiers de classe Java. Cette nouvelle API remplace  ASM, le framework de manipulation et d’analyse de bytecode Java.

Supposons que nous souhaitions générer la méthode suivante dans un fichier de classe :

void fooBar(boolean z, int x) {
  if (z)
    foo(x);
  else
    bar(x);
}

Le code suivant illustre la manière dont une méthode peut être générée à l’aide de Builder, mettant ainsi en évidence l’approche spécifique et transparente de l’API en matière de génération de code :

ClassBuilder classBuilder = ...;
classBuilder.withMethod(
  "fooBar", 
  MethodTypeDesc.of(CD_void, CD_boolean, CD_int), 
  flags,
  methodBuilder -> methodBuilder.withCode(
    codeBuilder -> {
      Label label1 = codeBuilder.newLabel();
      Label label2 = codeBuilder.newLabel();
      codeBuilder.iload(1)
                 .ifeq(label1)
                 .aload(0)
                 .iload(2)
                 .invokevirtual(ClassDesc.of("Foo"), "foo", 
                           MethodTypeDesc.of(CD_void, CD_int))
                 .goto_(label2)
                 .labelBinding(label1)
                 .aload(0)
                 .iload(2)
                 .invokevirtual(ClassDesc.of("Foo"), "bar", 
                           MethodTypeDesc.of(CD_void, CD_int))
                 .labelBinding(label2);
                 .return_();
    }
  )
);

 

Une première preview de ces modifications apparaît dans la JEP 457 de Java 22.

La JEP 466 de Java 23 apporte plusieurs améliorations :

  • Simplification de la classe CodeBuilder.
  • Les objets AttributesMapper dans Attributes sont désormais accessible via des méthodes statiques plutôt que des champs statiques. Cette nouveauté favorise l’initialisation tardive et réduit le coût de démarrage de Java
  • Signature.TypeArg a été révisé pour devenir un type de donnée algébrique.
  • Ajout des méthodes type-sensitives ClassReader.readEntryOrNull et ConstantPool.entryByIndex. Celles-ci lèvent désormais une ConstantPoolException plutôt qu’une ClassCastException, si l’entrée à l’index n’est pas du type attendu.
  • Amélioration de la classe ClassSignature pour représenter plus précisément les signatures génériques des superclasses et des superinterfaces.
  • Correction d’une erreur de nommage dans la classe TypeKind.
  • Suppression des détails d’implémentation dans ClassReader.


JEP 477: Implicitly Declared Classes and Instance Main Methods (Third Preview)

Apparue pour la première fois en preview dans Java 21, cette fonctionnalité vise à offrir aux débutants en Java la capacité d’écrire leurs premiers programmes sans nécessiter une compréhension approfondie des fonctionnalités conçues pour les programmes plus complexes.

En premier lieu, elle améliore le protocole de lancement des programmes Java en autorisant les méthodes principales d’instance. Ces méthodes ne sont pas static, ne sont pas nécessairement public, et n’ont pas besoin de posséder de paramètres dans leur signature.

class HelloWorld {
  void main() {
    System.out.println("Hello, World!");
  }
}

 

De plus, un fichier source est à présent en mesure de déclarer implicitement une classe (fichier HelloWorld.java) :

void main() {
  System.out.println("Hello, World!");
}

 

Classes implicitement déclarées

Les spécificités suivantes s’appliquent aux classes implicites :

  • Une classe implicite se situe toujours dans le package sans nom (tout comme une classe normale sans package/déclaration).
  • Une classe implicite est toujours final.
  • Une classe implicite n’est pas capable d’implémenter d’interfaces ni d’hériter d’autres classes. De même, aucune classe n’hérite d’une classe implicite.
  • Une classe implicite n’est pas accessible via le nom donné par le compilateur, c’est-à-dire que les autres classes n’instancient pas une classe implicite et n’y appellent pas de méthodes, pas même statiques.

Cependant, une classe implicite est capable d’appeler des méthodes sur elle-même, comme dans l’exemple suivant :

void main() {
  System.out.println(greeting());
}

String greeting() {
  return "Hello, World!";
}


Puisqu’une classe implicite n’est pas accessible de l’extérieur, elle doit contenir une méthode main().

Méthodes Main d’instance

Les méthodes main d’instance représentent des méthodes main non statiques. Les méthodes suivantes seront autorisées à l’avenir :

  • Méthodes d’instance non statiques,
  • Méthodes avec le niveau de visibilité public, protected ou package-private (sans modificateur),
  • Méthodes avec ou sans paramètres (String[]).

Voici quelques exemples:

  • void main()
  • void main(String[] args)
  • public void main()
  • protected static void main(String[] args)

Les méthodes statiques et non statiques ayant la même signature, ainsi que celles intégrant différents modificateurs de visibilité mais possédant la même signature, s’excluent mutuellement et conduisent à une erreur de compilation Already defined method.

Cependant, une méthode principale avec un String[] en paramètre et une méthode principale sans paramètre peuvent coexister :

void main(String[] args) {
  . . .
}

protected static void main() {
  . . .
}

 

Dans un tel cas, la méthode avec le paramètre (String[]) est démarrée.

 

Import automatique de java.io.IO

Enfin, la JEP 477 de Java 23 ajoute l’import implicite et automatique de la classe java.io.IO qui contient notamment les méthodes print() et println(). Elles offrent la possibilité d’omettre enfin le System.out du célèbre System.out.println().

Grâce à cet ajout, vous êtes désormais en capacité d’écrire le programme Java complet et valide suivant :

void main() {
  println("Hello world!");
}

 

JEP 480: Structured Concurrency (Third Preview)

La concurrence structurée est une approche moderne, rendue possible par les threads virtuels, pour diviser les tâches en sous-tâches et les exécuter en parallèle.

La concurrence structurée a été proposée et livrée dans la JEP 428 de Java 19 en tant qu’API d’incubation. Elle est réincubée via la JEP 437 de Java 20 avec une mise à jour mineure pour hériter des valeurs étendues. Elle a été présentée pour la première fois dans Java 21 via la JEP 453.

Dans Java 22 et maintenant 23, la fonctionnalité est présentée, à chaque fois sans changement, afin d’obtenir plus de retours.


JEP 481: Scoped Values (Third Preview)

Les Scopped Values offrent la possibilité de transmettre une ou plusieurs valeurs à une ou plusieurs méthodes, sans les définir comme paramètres explicites et sans les transmettre dans les appels successifs.

L’exemple suivant illustre la manière dont une API définit l’utilisateur connecté en tant que scopped value :

public class Api {
  public final static ScopedValue<User> LOGGED_IN_USER = 
                         ScopedValue.newInstance();
  . . .
  private void serve(Request request) {
    . . .
    User loggedInUser = authenticateUser(request);
    ScopedValue.where(LOGGED_IN_USER, loggedInUser)
               .run(() -> apiWebService.processRequest(request));
    . . .
  }
}

 

Supposons que le apiWebService appelé par le serveur appelle un service, et qu’à son tour, celui-ci appelle un Repository. Dans ce Repository, nous aurions alors la capacité d’accéder à l’utilisateur connecté comme suit :

public class Repository {
  . . .
  public Data getData(UUID id) {
    Data data = findById(id);
    User loggedInUser = Server.LOGGED_IN_USER.get();
    if (!loggedInUser.isAdmin()) {
      throw new UnauthorizedException(
                “Not authorized to access this data”);
    }
    return data;
  }
  . . .
}

 

 

Introduite dans Java 21, les Scopped Values entrent dans un troisième cycle de preview dans Java 23.

Pour cette troisième preview, la méthode ScopedValue.callWhere() est modifiée afin que le compilateur Java détecte si l’appel à la méthode est capable de lancer une Exception vérifiée.

Observons ci-dessous la fonction doSomethingSmart() déclarant lancer une SpecificException :

Result doSomethingSmart() throws SpecificException {
  . . .
}

Avec Java 22, lorsque nous entourions ScopedValue.callWhere() avec un try / catch, nous n’étions pas en mesure de spécifier une SpecificException dans le catch, mais uniquement une Exception générique :

// Java 22:
try {
  Result result = ScopedValue.callWhere(USER, 
                                loggedInUser, this::doSomethingSmart);
} 
catch (Exception e) { // ⟵ Catching generic Exception
. . .
}

 

Grâce à ces évolutions, nous avons la possibilité d’écrire :

/ Java 23:
try {
  Result result = ScopedValue.callWhere(USER, 
                                loggedInUser, () -> doSomethingSmart());
} 
catch (SpecificException e) { // ⟵ Catching SpecificException
  . . .
}

 

De plus, la méthode ScopedValue.getWhere(), qui aurait été employée dans notre cas, où doSomethingSmart() ne déclare pas lancer une Exception, n’est plus nécessaire : elle a été remplacée par ScopedValue.callWhere()

// Java 23:

Result doSomethingSmart() {
  . . .
}

Result result = callWhere(USER, loggedInUser, this::doSomethingSmart);

 

JEP 469: Vector API (Eighth Incubator)

Le Vector API entre dans sa huitième phase d’incubation, sans modifications notables.

Cette API rendra possible l’utilisation du calcul vectoriel avec les CPUs modernes, facilitant la parallélisation des opérations au sein d’un seul et même cycle de CPU.

Requérant les fonctions du Projet Valhalla, ces deux développements devraient être disponibles prochainement en preview simultanément.

#03

Java 23 : dépréciations et suppressions

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

La classe sun.misc.Unsafe a été introduite en 2002 avec Java 1.4. La plupart de ses méthodes offrent un accès direct à la mémoire, à la fois à la heap memory mais aussi à la mémoire non contrôlée de la heap memory, c’est-à-dire à la mémoire native.

Son utilisation a été supplanté par les API standard, introduites dans les versions successives de Java :

  • L’API VarHandle de Java 9 propose l’accès direct à la heap memory.
  • Depuis Java 22 et la JEP 454, nous sommes capables d’invoquer des fonctions de la mémoire native.

Thread.suspend/resume and ThreadGroup.suspend/resume are Removed

Les méthodes Thread.suspend(), Thread.resume(), ThreadGroup.suspend(), ThreadGroup.resume(), menant à des deadlocks et marquées comme dépréciées depuis Java 1.2 sont retirées.

Remove ThreadGroup.stop

Marquée comme dépréciée depuis Java 1.2, en raison de son caractère intrasèquement non sécurisé, la méthode ThreadGroup.stop() est désormais supprimée.

#04

Java 23 : autres évolutions

JEP 474: ZGC: Generational Mode by Default

Cette JEP établit le mode générationnel comme mode par défaut pour le Z Garbage Collector (ZGC), tout en dépréciant par le mode non-générationnel. Le mode générationnel étant généralement plus performant que le mode non-générationnel, il deviendra le principal axe des futurs développements.

Removal of Module jdk.random

Les implémentations du module java.random sont déplacées vers le module java.base. Le module java.random est ainsi retiré de la JDK.

Console Methods With Explicit Locale

Un paramètre Locale est ajouté dans les signatures des méthodes format(), printf(), readLine() et readPassword() de java.io.Console. Java 23 offre désormais la possibilité d’écrire :

System.console().printf(Locale.FRANCE, "%1$tY-%1$tB-%1$te %1$tA", 
                        new Date())


La sortie correspondante sera 2024-mai-16 jeudi.

Support for Duration Until Another Instant

La méthode Instant.until(Instant) est introduite afin de calculer la durée entre deux objets Instant. Auparavant, nous étions contraints d’utiliser Duration.between(Instant, Instant) pour obtenir cette durée.

Cette version intermédiaire entre deux LTS apporte plusieurs améliorations notables. Tout d’abord, elle introduit une mise à jour de la JavaDoc, proposant désormais son écriture en Markdown.

La nouvelle fonctionnalité en preview concernant les imports de modules est particulièrement intéressante, car elle réduira considérablement la taille des imports, ce qui est très avantageux pour les implémentations utilisant de nombreuses classes.

La JEP 455 facilite le casting des types primitifs de manière rapide et élégante, bien que les cas d’utilisation soient encore relatiement limités.

La JEP 482, une évolution de la JEP 447, offre une flexibilité accrue dans le constructeur d’une classe dérivée.

Les JEPs 466, 477 et 481 apportent des améliorations mineures, tandis que les JEPs 473, 480 et 469 sont proposées à nouveau sans modification

Sources :

Source de l’étude : https://openjdk.org/projects/jdk/23/

En savoir plus sur la notion de JEP : https://openjdk.org/jeps/

Vous souhaitez échanger avec un expert ?

Contactez-nous