Java 9 – les fabriques pour collections immuables

Java ne propose pas de syntaxe dédiée et simple pour créer une collection d’éléments : il faut utiliser les classes de l’API Collection. La quantité de code à fournir pour une collection immuable est encore un peu plus importante.

    List titresCours = new ArrayList();
    titresCours.add("Optimisations Java");
    titresCours.add("Architecture de services légère avec REST et Java");
    titresCours.add("Développer une application web avec Angular");
    titresCours.add("Kanban");
    titresCours = Collections.unmodifiableList(titresCours);

Il est possible d’utiliser différentes syntaxes pour obtenir un résultat similaire.

    List titresCours = Collections.unmodifiableList(
      new ArrayList(Arrays.asList("Optimisations Java", 
                                    "Architecture de services légère avec REST et Java", 
                                    "Développer une application web avec Angular ", 
                                    "Kanban")));
    List titresCours = Collections.unmodifiableList(
      new ArrayList() {{
         add("Optimisations Java ");
         add("Architecture de services légère avec REST et Java");
         add("Développer une application web avec Angular");
         add("Kanban"); }});

A partir de Java 8, il est aussi possible d’utiliser un Stream pour créer la collection

    List titresCours = Collections.unmodifiableList(
      Stream.of("Optimisations Java", 
                "Architecture de services légère avec REST et Java", 
                "Développer une application web avec Angular", 
                "Kanban")
            .collect(Collectors.toList()));

Toutes ses solutions sont verbeuses et possèdent des inconvénients notamment en termes de consommation de ressources.
Il est enfin possible d’utiliser des bibliothèques tierces comme Guava par exemple qui propose la classe ImmutableList.
 

La JEP 269 (Convenience Factory Methods for Collections)

La JEP 269, intégrée dans Java 9, propose l’ajout de fabriques dans les interfaces Set, List et Map. Le but est de faciliter la création d’instances de Collection immuables en limitant l’empreinte mémoire.

Plusieurs langages de programmation offrent une syntaxe dédiée pour obtenir une collection contenant des littéraux, par exemple Groovy :

    def values = [("Optimisations Java", 
                   "Architecture de services légère avec REST et Java", 
                   "Développer une application web avec Angular", 
                   "Kanban"] as Set

Le choix a été fait de ne pas utiliser une syntaxe dédiée dans le langage Java mais d’utiliser des fabriques. Ces fabriques peuvent être utilisées pour créer des collections immuables vides ou contenant un nombre restreint d’éléments.

L’implémentation repose sur la possibilité d’ajouter des méthodes statiques dans une interface depuis Java 8. Pour des raisons de performance, l’API propose 12 surcharges de la fabrique attendant de zéro à dix éléments et attendant un varargs d’éléments si la collection doit avoir plus de 10 éléments.

    List titresCours = List.of("Optimisations Java", 
                               "Architecture de services légère avec REST et Java", 
                               "Développer une application web avec Angular", 
                               "Kanban");

Les instances obtenues sont immuables : toute invocation d’une méthode de la collection permettant de la modifier lève une exception de type java.lang.UnsupportedOperationException.

L’instance obtenue est sérialisable si les éléments qu’elle contient sont sérialisables.

L’utilisation des fabriques pour Collection immuables impliquent quelques contraintes :

  • il n’est pas possible d’utiliser des valeurs null dans List et Set
  • il n’est pas possible d’utiliser null comme clé ou valeur dans les Map
  • l’ordre de parcours des éléments des collections de type Set et Map est volontairement aléatoire
  • les instances obtenues sont value based : il ne faut pas utiliser leur identité car la fabrique peut renvoyer une nouvelle instance ou une instance existante

 

La fabrique of() de l’interface List

Elle attend en paramètre les éléments contenus dans la List. La surcharge sans paramètre permet de créer une instance immuable vide.

 

La fabrique of() de l’interface Set

C’est une fabrique, similaire à celle de l’interface List, permettant de créer des instances immuables de type Set.

    Set titresCours = Set.of("Optimisations Java", 
                                     "Architecture de services légère avec REST et Java", 
                                     "Développer une application web avec Angular",
                                     "Kanban");

La présence de valeurs en double dans les paramètres lève une exception de type IllegalArgumentException.

 

Les fabriques of() et ofEntries de l’interface Map

C’est une fabrique, similaire à celle de l’interface List, permettant de créer des instances immuables de type Map. Elle possède 12 surcharges, acceptant de zéro à vingt paramètres, permettant de fournir les paires clé/valeur de chaque élément de la Map.

    Map codeCours = Map.of("JA", "Java", 
                                           "UL", "Usine Logicielle",
                                           "MD", "Modelisation");

La méthode ofEntries() de l’interface Map est une fabrique qui permet de créer des instances immuables de type Map. Elle attend en paramètre un varargs de type Map.Entry.

Pour faciliter la création d’instances immuables de type Map.Entry, l’interface Map propose la fabrique static entry() qui attend en paramètre la clé et la valeur.

    Map codeCours = Map.ofEntries(Map.entry("JA", "Java"),
                                                  Map.entry("UL", "Usine Logicielle"),
                                                  Map.entry("MD", "Modelisation"));

Avec un import static sur la méthode entry() de l’interface Map, le code devient encore plus concis :

    Map codeCours = Map.ofEntries(entry("JA", "Java"),
                                                  entry("UL", "Usine Logicielle"),
                                                  entry("MD", "Modelisation"));

 

Conclusion

Les fabriques de Collection immuables permettent de simplifier et réduire l’empreinte mémoire de la création d’instances de collections immuables sans modifier la syntaxe du langage Java.

L’ajout de ces fabriques peut sembler anodin mais l’utilisation de classes immuables est un des principes à utiliser pour une programmation concurrente fiable et robuste.