Java 17 : quoi de neuf ?
Maintenant que Java 17 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 16, Java 15, Java 14, Java 13, Java 12, Java 11, Java 10, et Java 9.
Cette release ne contient pas beaucoup de JEP, et donc pas beaucoup de nouveautés importantes. Par contre, elle apporte quelques petites additions sympathiques, et son statut de LTS (Long Term Support) en fait une release importante.
JEP 406: Pattern Matching for switch (Preview)
C’est sans doute la plus grande nouveauté de cette release, le pattern matching arrive dans les switchs, en preview. On peut désormais faire un switch sur le type d’une variable (y compris enum, record et tableau), et en extraire une variable locale au case qui sera du type correspondant.
La JEP donne l’exemple suivant avec une chaîne de if/else :
static String formatter(Object o) { String formatted = "unknown"; if (o instanceof Integer i) { formatted = String.format("int %d", i); } else if (o instanceof Long l) { formatted = String.format("long %d", l); } else if (o instanceof Double d) { formatted = String.format("double %f", d); } else if (o instanceof String s) { formatted = String.format("String %s", s); } return formatted; }
Qui peut maintenant être ré-écrit avec une switch expression :
static String formatterPatternSwitch(Object o) { return switch (o) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }; }
En plus de supporter du pattern matching, le switch permet maintenant de définir un case spécial null
(dans ses deux formes : statement ou expression). Auparavant, une variable de switch nulle entraînait une NullPointerException
. Dans sa nouvelle forme, on peut ajouter un case null
pour gérer les nulls au sein du switch. Sans case null
, l’ancien comportement est gardé, et une NullPointerException
sera levée.
Si on reprend l’exemple précédent, cela nous donne :
static String formatterPatternSwitch(Object o) { return switch (o) { case null -> "null"; case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }; }
Le grand intérêt est que cela évite de devoir faire un test défensif avant le switch, et permet d’inclure dans celui-ci la valeur nulle comme toute autre valeur possible de notre variable.
Et ça ne s’arrête pas là, le switch a été enrichi de guards qui permettent d’inclure une condition au case
. L’exemple suivant issu de la JEP montre son utilisation. En ajoutant un guard au case Triangle : case Triangle t && (t.calculateArea() > 100)
, on peut créer deux cases : un pour les grands triangles, et un autre pour les petits.
static void testTriangle(Shape s) { switch (s) { case Triangle t && (t.calculateArea() > 100) -> System.out.println("Large triangle"); case Triangle t -> System.out.println("Small triangle"); default -> System.out.println("Non-triangle"); } }
Plus d’informations dans la JEP-406.
JEP 356: Enhanced Pseudo-Random Number Generators
La JEP-356 fournit une nouvelle interface RandomGenerator
, et une factory RandomGeneratorFactory
, qui permettent d’accéder à une implémentation de générateur de nombre aléatoire. Les générateurs existant : Random
, SecureRandom
, SplittableRandom
et ThreadLocalRandom
; implémentent maintenant cette interface qui ajoute, entre autre, l’accès à un stream de nombre aléatoire (RandomGenerator::doubles()
, RandomGenerator::ints()
, …).
De nouveaux algorithmes de génération de nombre aléatoires ont été implémentés, plus sécurisés et plus performant (mais ils ne sont plus thread-safe), ils ont vocation à remplacer les anciens.
Voici quelques exemples d’instanciation de générateur de nombres aléatoires :
// the old Random generator RandomGenerator rng1 = RandomGeneratorFactory.of("Random").create(42); // the default random generator, currently jdk.random.L32X64MixRandom but this can change RandomGenerator rng2 = RandomGeneratorFactory.getDefault().create(42); // shortcut for the default RandomGenerator rng3 = RandomGenerator.getDefault(); // stream all available generators and display their names RandomGeneratorFactory.all().forEach(generator -> System.out.println(generator.name());
Plus d’informations dans la JEP-356 ou dans cet article très complet de
Michael Bien : Java 17’s Enhanced Pseudo-Random Number Generators.
Les fonctionnalités qui passent de preview à standard
Dans Java 17, une seule fonctionnalité passe de preview à standard : les Sealed Classes (JEP-409), vous pouvez vous référez à mes articles précédents pour plus d’informations.
Les fonctionnalités qui restent en preview
Les fonctionnalités suivantes restent en preview (ou en incubator module).
Pour les détails sur celles-ci, vous pouvez vous référer à mes articles précédents.
- JEP-414 – Vector API : seconde incubation de la fonctionnalité qui intègre des améliorations au sein de l’API et une meilleur performance.
- JEP-412 – Foreign Function & Memory API : nouvel incubator pour ces deux fonctionnalités qui sont maintenant liées (l’une utilisant l’autre) au sein d’un même incubator.
Un nouveau port de la JVM
Java 17 ajoute le support de l’architecture macOS/AArch64 (aka Apple Silicon M1). Plus d’informations à ce sujet dans la JEP-391.
HexFormat
La class java.util.HexFormat
permet la conversion de type primitif, tableau de byte, ou tableau de char en chaîne de caractère hexadécimal et vice versa.
HexFormat.of().toHexDigits(127); // "7f" HexFormat.of().fromHexDigits("7f"); // 127
Plus d’information dans le ticket JDK-8251989.
InstantSource
Tester du code contenant de la manipulation de date a toujours été un challenge, surtout si celui-ci use et abuse de System.currentTimeMillis()
, LocalDateTime.now()
, et autre initialisation de date avec la date en cours.
Pour faciliter la testabilité de ce genre de code, une nouvelle interface a été ajoutée au JDK : InstantSource
, avec une seule implémentation : Clock
. Le but de l’interface InstantSource
est d’être une fabrique d’Instant
. Au lieu de créer un Instant
avec la date du jour, vous le créez depuis l’InstantSource
. Un test pouvant alors utiliser une InstantSource
à une date fixe au lieu de la date du jour.
Imaginez le code suivant :
public class MyBean { private InstantSource source; // dependency inject ... public void process(Instant endInstant) { if (source.instant().isAfter(endInstant) { ... } } }
En fonctionnement normal, l’InstanSource
est initialisé via InstantSource.system()
, et en test via InstantSource.fixed(LocaDateTime.of(//the hardcoded date time))
.
Divers
Divers ajouts au JDK :
Map.Entry.copyOf(Map.Entry)
: permet de créer une copie d’une entrée de Map qui ne soit pas connectée à la Map existante.Process.inputReader(), Process.outputWritter(), Process.errorReader()
: permet d’accéder aux entrée, sortie standard, et sortie erreur d’un process via un Reader ou un Writter.
Dépréciation et encapsulation
Java 17 voit la dépréciation pour suppression de l’API des Applets via la JEP-398, celle-ci n’étant plus utilisée depuis de nombreuses années, elle n’a pas fait couler beaucoup d’encre.
Java 17 voit aussi la dépréciation du Security Manager pour suppression via la JEP-411. Il y a eu beaucoup de débat autour de cette annonce, avec, il faut l’avouer un peu de drama, comme quand Apache Netbeans a annoncé qu’il ne pourrait pas supporter Java 17 alors qu’il fallait juste changer quelques lignes de code …
La suppression du Security Manager a été annoncée, car celui-ci est complexe à maintenir, coûteux en termes de performance, et n’apporte pas la sécurité nécessaire face aux enjeux actuels. Il a été créé pour sécuriser les applets qui, par définition, exécutent du code untrusted, et n’a donc plus de sens dans une JVM qui ne contiendrait plus l’API Applet.
Pour plus d’info sur sa suppression, vous pouvez lire l’article d’InfoQ ou ce thread reddit (j’ai beaucoup apprécié les réponses de pron98).
Pour finir, la JEP 403: Strongly Encapsulate JDK Internals a basculé le JDK vers une encapsulation stricte de ses classes internes. C’est la fin de ce qui avait été commencé avec Java 9 et la modularisation du JDK.
Concrètement, le mode d’encapsulation était passé de --illegal-access=permit
en Java 15 à --illegal-access=deny
en Java 16 avec la possibilité de changer l’option de configuration. Avec Java 17, --illegal-access
disparaît et l’accès aux classes internes du JDK (hors Unsafe) n’est plus possible.