La sortie du Java Development Kit (JDK) 23, en septembre 2024, marque une étape importante dans l'histoire du langage de programmation Java, un outil robuste, polyvalent et essentiel dans l'environnement du développement logiciel.

 

Depuis 1995, lorsque Java a été présenté pour la première fois par Sun Microsystems avec le slogan "Write Once, Run Anywhere", il permet de faire tourner du code sur n'importe quel appareil. Au fil des années, Java a évolué à travers différentes versions, introduisant de nouvelles fonctionnalités et des améliorations tout en maintenant la compatibilité avec les versions antérieures.


Cette tradition se poursuit avec cette nouvelle version, qui vise à améliorer la productivité des développeurs et les performances des applications, tout en s'assurant que Java reste pertinent et puissant.

 

 

Quoi de neuf dans Java 23 ?

Cette version comprend un total de 12 propositions d'amélioration de Java (Java Enhancement Proposals, ou JEPs), à savoir :


Nouvelles fonctionnalités
  • Types primitifs dans les motifs, instanceof, et switch (Primitive Types in Patterns, instanceof, and switch) – fonctionnalité en aperçu
  • Commentaires de documentation en Markdown (Markdown Documentation Comments)
  • Déclarations d'importation de module (Module Import Declarations) – fonctionnalité en aperçu

 

Fonctionnalités améliorées
  • API des fichiers de classe (Class-File API) – deuxième aperçu
  • API vectorielle (Vector API) – huitième incubation
  • Collecteurs de flux (Stream Gatherers) – deuxième aperçu
  • ZGC : Mode générationnel par défaut (ZGC : Generational Mode by Default)
  • Classes déclarées implicitement et méthodes main d'instance (Implicitly Declared Classes and Instance Main Methods) – troisième aperçu
  • Concurrence structurée (Structured Concurrency) – troisième aperçu
  • Valeurs à portée (Scoped Values) – troisième aperçu
  • Corps de constructeur flexible (Flexible Constructor Bodies) – deuxième aperçu

 

Fonctionnalités dépréciées
  • Déprécation des méthodes d'accès à la mémoire dans sun.misc.Unsafe en vue de leur suppression (Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal)

 

Regardons de plus près certaines des mises à jour, selon nous pertinentes.

 

 

Quelques améliorations du langage

Types primitifs dans les motifs, instanceof, et switch

Cette nouvelle fonctionnalité a pour objectifs de :

  • Permettre une exploration uniforme des données en autorisant les motifs de type pour tous les types, qu'ils soient primitifs ou de référence.
  • Aligner les motifs de type avec instanceof et synchroniser instanceof avec les conversions sécurisées.
  • Permettre à la correspondance de motifs d'utiliser des motifs de type primitif, à la fois dans des contextes imbriqués et au niveau supérieur.
  • Fournir des constructions qui éliminent le risque de perte d'informations dû à des conversions non sécurisées.
  • Permettre à switch de traiter les valeurs de tout type primitif. Cette amélioration rend le langage Java plus uniforme et expressif.

 

Exemple :

//improve the switch expression:

switch (x.getStatus()) {

    case 0 -> "ok";

    case 1 -> "warning";

    case 2 -> "error";

    default -> "unknown status: " + x.getStatus();

}

//exposing the matched value:

switch (x.getStatus()) {

    case 0 -> "okay";

    case 1 -> "warning";

    case 2 -> "error";

    case int i -> "unknown status: " + i;

}

//allowing guards to inspect the corresponding value:

switch (x.getYearlyFlights()) {

    case 0 -> ...;

    case 1 -> ...;

    case 2 -> issueDiscount();

    case int i when i >= 100 -> issueGoldCard();

    case int i -> ... appropriate action when i > 2 && i < 100 ...

}

 

Pour plus d'informations sur cette fonctionnalité, cliquez ici.

 

 

Corps de Constructeur Flexible

Dans le langage de programmation Java, les constructeurs permettent de placer des instructions avant un appel explicite à un constructeur, tel que super(..)ou this(..). Bien que ces instructions ne puissent pas référencer l'instance en cours de construction, elles peuvent initialiser ses champs.


Initialiser les champs avant d'invoquer un autre constructeur améliore la fiabilité des classes, en particulier lorsque des méthodes sont surchargées. Cette fonctionnalité est actuellement en phase d'aperçu.

 

Exemple :

//Flexible Constructor Bodies

class Parent {

    int x;

 

    public Parent(int x) {

        this.x = x;

    }

}

 

class Child extends Parent {

    int y;

 

    public Child(int x, int y) {

        // Statements before calling the parent constructor

        int temp = x * 2; // Cannot reference instance fields

        super(temp); // Explicit constructor invocation

        this.y = y; // Instance fields can be initialized after the invocation

    }

}

 

public class Main {

    public static void main(String[] args) {

        Child child = new Child(5, 10);

        System.out.println("x: " + child.x + ", y: " + child.y); // Outputs: x: 10, y: 10

    }

}

 

Pour plus d'informations sur cette fonctionnalité, cliquez ici.

 

 

Concurrence Structurée

La fonctionnalité de concurrence structurée simplifie la programmation multithread en traitant plusieurs tâches exécutées dans différents threads comme une seule unité de travail, ce qui facilite la gestion des erreurs et l'annulation.

 

Exemple :

 

// Structured Concurrency

public class StructuredConcurrencyExample {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

            Future<String> future1 = scope.fork(() -> fetchDataFromService1());

            Future<String> future2 = scope.fork(() -> fetchDataFromService2());

 

            scope.join(); // Join both forks

            scope.throwIfFailed(); // Propagate exceptions

 

            String result1 = future1.resultNow();

            String result2 = future2.resultNow();

            System.out.println(result1 + " " + result2);

        }

    }

 

    private static String fetchDataFromService1() {

        // Simulate fetching data

        return "Data1";

    }

 

    private static String fetchDataFromService2() {

        // Simulate fetching data

        return "Data2";

    }

}


Pour plus d'informations sur cette fonctionnalité, cliquez ici.

 

 

Valeurs à Portée

Les valeurs à portée permettent aux méthodes de partager des données immuables avec leurs sous-fonctions au sein d'un thread et avec les threads enfants. Elles sont plus simples à comprendre par rapport aux variables locales de thread et offrent des coûts moindres en termes d'espace et de temps.


Lorsqu'elles sont utilisées avec les threads virtuels et la concurrence structurée, les valeurs à portée sont particulièrement efficaces. Cette fonctionnalité est actuellement une API en aperçu.

 

Exemple :

 

import java.util.concurrent.Executors;

import java.util.concurrent.ExecutorService;

import jdk.incubator.concurrent.ScopedValue;

 

public class Main {

    public static void main(String[] args) {

        ScopedValue<String> scopedValue = ScopedValue.create("Hello, World!");

 

        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.submit(() -> {

            System.out.println("Scoped Value: " + scopedValue.get());

        });

 

        executor.shutdown();

    }

}


Pour plus d'informations sur cette fonctionnalité, cliquez ici.

 

 

Collecteurs de Flux

Cette fonctionnalité vise à améliorer l'API des flux Java en introduisant deux nouvelles opérations terminales : Stream.gather(toList(), toSet()) et Stream.gather(toMap(), toSet()). Ces opérations permettent de collecter les éléments d'un flux dans plusieurs collections simultanément, ce qui simplifie le code et améliore la lisibilité lorsque le flux doit être collecté dans plusieurs conteneurs.


La fonctionnalité, initialement proposée dans Java 22, a été affinée en fonction des retours et est maintenant présentée pour un deuxième aperçu afin de s'assurer que sa fonctionnalité et ses performances répondent aux besoins de la communauté avant sa finalisation.

 

Exemple :

import java.util.List;

import java.util.Set;

import java.util.stream.Collectors;

import java.util.stream.Stream;

 

public class StreamGatherersExample {

    public static void main(String[] args) {

        // Example stream

        Stream<String> stream = Stream.of("apple", "banana", "cherry", "apple", "date", "banana");

 

        // Using Stream.gather to collect elements into a List and a Set simultaneously

        var result = stream.collect(Collectors.gather(

            Collectors.toList(),

            Collectors.toSet()

        ));

 

        // Extracting the results

        List<String> list = result.getFirst();

        Set<String> set = result.getSecond();

 

        // Printing the results

        System.out.println("List: " + list);

        System.out.println("Set: " + set);

    }

}

 

Pour plus d'informations sur cette fonctionnalité, cliquez ici.

 

 

Meilleures pratiques pour le développement avec Java 23

Voici mes conseils pour les développeurs Java utilisant la nouvelle version Java 23 :

 

Adoptez des techniques de codage efficaces :

  1. Utilisez la correspondance de motifs pour les instructions switch : cela simplifie la logique du code et réduit les structures imbriquées, rendant le code plus concis et lisible.
  2. Optimisez les enregistrements (records) : personnalisez le comportement de la sérialisation pour améliorer l'encapsulation des données et simplifier les pratiques de programmation orientée objet. Cela favorise l'efficacité et la maintenabilité du code.
  3. Exploitez les améliorations de l'API vectorielle : profitez des améliorations de l'API vectorielle pour les calculs numériques, afin d'augmenter les performances, notamment pour les opérations matricielles et le calcul scientifique.


Directives pour la gestion des erreurs :

  1. Adoptez des cadres de sérialisation modernes : passez des anciennes API de sérialisation aux cadres modernes tels que Protocol Buffers ou JSON pour assurer la compatibilité future et améliorer les capacités d'échange de données.
  2. Mettez en œuvre des pratiques de codage sécurisé : adoptez les principes de codage sécurisé et les outils fournis dans Java 23 pour atténuer les vulnérabilités telles que les attaques par injection et l'exposition des données, renforçant ainsi la sécurité des applications.
  3. Affinez la collecte des déchets (Garbage Collection) : optimisez les algorithmes de collecte des déchets pour minimiser les pauses et optimiser l'utilisation de la mémoire, contribuant ainsi à une performance plus fluide de l'application et à une réduction de la surcharge des ressources.

 

Stratégies d'optimisation des performances :

  1. Utilisez les améliorations de la compilation Just-In-Time (JIT) : tirez parti des optimisations du compilateur JIT pour compiler dynamiquement les chemins de code fréquemment exécutés, ce qui se traduit par des vitesses d'exécution plus rapides et des temps de réponse améliorés.
  2. Optimisez les performances du client HTTP : exploitez les optimisations du client HTTP pour améliorer la communication avec les services web, y compris une meilleure gestion du pooling de connexions et du traitement des requêtes/réponses.
  3. Réduisez l'empreinte mémoire : appliquez des techniques de gestion de la mémoire pour réduire l'empreinte mémoire des applications Java, garantissant une allocation et une utilisation plus efficaces de la mémoire.

 

 

Conclusion

Java 23 introduit un ensemble de fonctionnalités et d'améliorations avancées conçues pour améliorer l'efficacité et les performances du développement logiciel, tout en renforçant la compatibilité et l'interopérabilité de Java avec d'autres langages et plateformes. Cette version inclut des améliorations de la Java Virtual Machine (JVM), de nouvelles fonctionnalités de langage, et des mises à jour des API et bibliothèques existantes, le tout visant à fournir aux développeurs davantage d'outils pour écrire un code performant et maintenable. Parmi les fonctionnalités notables, on trouve les améliorations de la correspondance de motifs pour les expressions switch, les améliorations de l'API des fonctions et de la mémoire étrangères, ainsi que de nouvelles options pour la collecte des déchets, qui contribuent collectivement à la robustesse et à l'adaptabilité des applications Java dans des environnements diversifiés.


En tant que développeur Java expérimenté, je trouve les nouvelles fonctionnalités de Java 23 à la fois passionnantes et essentielles pour le développement moderne. Les améliorations de la correspondance de motifs simplifient la logique conditionnelle complexe, rendant le code plus lisible et plus facile à maintenir. Les améliorations de l'API des fonctions et de la mémoire étrangères sont particulièrement importantes car elles ouvrent de nouvelles possibilités pour l'intégration de Java avec du code natif et d'autres langages, renforçant la polyvalence de Java dans les écosystèmes multilingues.


Dans l'ensemble, Java 23 poursuit la tradition d'évolution du langage et de la plateforme pour répondre aux besoins contemporains du développement, garantissant que Java reste un choix de premier plan pour les développeurs du monde entier.

Partager cet article