Tutoriel pour découvrir l’API Collections de Google Guava

La classe Lists

RAUZDUEL Rony

Le but de cet article est l'exploration de la classe Lists de l'API Collections de la bibliothèque Guava de Google.

Comme exemple nous utiliserons un parc automobile, cela nous permettra de sonder de façon pratique et ludique les méthodes de cette classe.

En effet de par mon expérience, j'ai pu constater que certains développeurs (et parfois moi aussi) implémentaient des fonctionnalités alors que ces dernières étaient présentes dans des bibliothèques tierces.

Ainsi dans nos futurs projets respectons le vieil adage : "ne réinventez pas la roue".

Par ailleurs, je vous invite à créer un fil de discussion afin d’y laisser tous vos commentaires pour l’appréciation, voire pour l’amélioration des futurs tutoriels. Commentez

- Titre : Tutoriel pour découvrir l’API Collections de Google Guava

- Auteur : RAUZDUEL Rony

- Parution : 1 mars 2021

- Mise à jour : 3 novembre 2021

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2021 RAUZDUEL Rony. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Qu’est-ce que l’API Google Guava et pourquoi l’utiliser ?

- III. Ajouter Google Guava à votre projet

- IV. La classe utilitaire Lists

- V. Implémentation des méthodes de la classe Lists

- V-A. Les imports

- V-A-1. Les attributs et blocs statiques

- V-B. La méthode asList

- V-B-1. asList(E first, E[] rest)

- V-B-2. asList(E first, E second, E[] rest)

- V-C. La méthode cartesianProduct

- V-C-1. cartesianProduct(List<? extends B> …lists)

- V-C-2. cartesianProduct(List<? extends List<? extends B>> lists)

- V-D. La méthode charactersOf

- V-D-1. charactersOf(CharSequence sequence)

- V-D-2. charactersOf(String string)

- V-E. La méthode newArrayList

- V-E-1. newArrayList()

- V-E-2. newArrayList(E… elements)

- V-F. La méthode LinkedList

- V-G. La méthode partition(List<T> list, int size)

- V-H. La méthode reverse(List<T> list)

- V-I. La méthode transform(List<F> fromList, Function<? super F,? extends T> function)

- VI. Conclusion

- VII. Remerciements

I - Introduction

Dans ce premier tutoriel sur l’exploration de la bibliothèque Guava de Google, nous allons parcourir les méthodes de la classe Lists afin de découvrir comment les exploiter de façon efficiente dans nos futurs projets. Ce tutoriel étant le premier d’une longue série à venir.

Par ailleurs comme il s'agit d'une découverte, je parcours la bibliothèque des méthodes de la classe Lists en y greffant des exemples simples. Force est de constater que la façon la plus triviale et adéquate de comprendre les fonctionnalités d’une classe utilitaire est d’implémenter chacune de ses méthodes à l’aide d’exemple. Ce tutoriel est avant tout pratique, mon fil conducteur est la page suivante https://github.com/google/guava/wiki/CollectionUtilitiesExplained#lists traitant des Collections de l'API Google Guava. Le lecteur devra donc s’attendre à voir la mise en œuvre des méthodes de la classe Lists de façon pragmatique et ludique. Souhaitons au lecteur d’apprécier toute la richesse de cette bibliothèque qui ne fait que s’améliorer au fur et à mesure des versions. Bonne lecture à tous.

II - Qu’est-ce que l’API Google Guava et pourquoi l’utiliser ?

L’API Google Guava est une bibliothèque contenant un ensemble de classes utilitaires fort utiles dans n’importe quel projet Java. En effet, cette API contient des classes allant du traitement des collections permettant de gérer des objets immuables, à la manipulation des chaînes de caractères, en passant par la gestion des entrées-sorties conversationnelles, et bien d’autres fonctionnalités qui facilitent la vie du développeur.

Dans cette série de tutoriels, nous n’aborderons que les classes utilitaires de l’API Collections. Cette dernière regroupe un ensemble de classes analogues à celles présentes dans le JDK, mais à la différence que ces dernières ont été améliorées par rapport à celles du JDK d’ORACLE.

En effet les développeurs de Google ont souhaité étendre les fonctionnalités des structures de données natives que nous utilisons au quotidien afin d’accroître notre productivité.

Le projet Guava représente le cœur du développement de Google, il représente des années de développement et une boîte à outils utilisée au quotidien par les développeurs de Google. Cette API est arrivée à maturité puisqu’elle est à l’heure actuelle à sa version 30.1 et ne cesse d’être agrémentée de nouvelles fonctionnalités.

Pour conclure cette section, nous dirons que dans ce premier volet nous découvrirons l’ensemble des méthodes utilitaires statiques composant la classe utilitaire Lists.

III - Ajouter Google Guava à votre projet

La bibliothèque Google Guava peut être ajoutée de plusieurs façons dans un projet. La façon la plus triviale est de récupérer le .jar depuis le site de Google, puis de placer ce dernier directement dans la bibliothèque de votre projet.

Une autre façon plus élégante est d’utiliser un outil industrialisé de build comme Apache Maven ou Gradle en y ajoutant la dépendance requise de Google Guava.

Installer Guava avec Maven

1. <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->

2. <dependency>

3. <groupId>com.google.guava</groupId>

4. <artifactId>guava</artifactId>

5. <version>30.1-jre</version>

6. </dependency>

IV - La classe utilitaire Lists

Comme je l’ai souligné, tout développeur familier avec le langage Java connaît l’utilité pratique des structures de données implémentant l’interface java.util.Collections (List, Map, Set, etc.).

Google Guava est allé encore plus loin dans l’implémentation des fonctionnalités sous-jacentes à l’utilisation de ces structures de données. Les développeurs de Google ont conçu de nombreuses méthodes utilitaires statiques permettant une gestion efficiente lorsque l’on travaille avec des objets de type List.

La classe com.google.common.collect.Lists contient des méthodes utilitaires statiques permettant de créer des instances de l'interface List du JDK.

Mieux qu’un long discours dans le chapitre suivant, nous mettrons en œuvre la richesse de cette bibliothèque à travers un cas pratique de gestion d’un parc automobile.

V - Implémentation des méthodes de la classe Lists

V-A - Les imports

Les imports

1. import com.google.common.collect.ImmutableList;

2. import com.google.common.collect.Lists;

3. import static java.lang.System.out;

4. import java.util.ArrayList;

5. import java.util.Arrays;

6. import java.util.LinkedList;

7. import java.util.List;

8. import java.util.function.Function;

V-A-1 - Les attributs et blocs statiques

Créons une classe Application qui contiendra les attributs, les blocs et les méthodes statiques pour la gestion de notre parc automobile.

Les attributs et blocs statiques

1. public final class Application {

2. private final static int NB_MARQUES_VEHICULES = 5;

3. private final static String[] peugeot;

4. private final static String[] toyota;

5. private final static String[] opel;

6. private final static String[] renault;

7. private final static String[] citroen;

8. private final static List<String> carburants; // types de carburants

9. private final static List<Float> consommations;

11. static { // les marques des véhicules

12. peugeot = new String[NB_MARQUES_VEHICULES];

13. peugeot[0] = "Peugeot Nouvelle SUV 3008";

14. peugeot[1] = "Peugeot Nouvelle SUV 5008";

15. peugeot[2] = "Peugeot Traveller/Expert Combi";

16. peugeot[3] = "Peugeot Boxer";

17. peugeot[4] = "Peugeot Tepee";

19. toyota = new String[NB_MARQUES_VEHICULES];

20. toyota[0] = "Toyota Auris";

21. toyota[1] = "Toyota Nouvelle Yaris";

22. toyota[2] = "Toyota Aygo X ";

23. toyota[3] = "Toyota Prius+";

24. toyota[4] = "Toyota Nouvelle C-HR";

26. opel = new String[NB_MARQUES_VEHICULES];

27. opel[0] = "Opel Calibra";

28. opel[1] = "Opel Ascona";

29. opel[2] = "Opel Corsa";

30. opel[3] = "Opel Alambra";

31. opel[4] = "Opel Meriva";

33. renault = new String[NB_MARQUES_VEHICULES];

34. renault[0] = "Renault Kangoo VP";

35. renault[1] = "Renault Nouveau Grand Scenic";

36. renault[2] = "Renault Alaskan";

37. renault[3] = "Talisman Estate";

38. renault[4] = "Renault Safrane";

40. citroen = new String[NB_MARQUES_VEHICULES];

41. citroen[0] = "Citroen Nouvelle C-Elysée";

42. citroen[1] = "Citroen Nemo Multispace";

43. citroen[2] = "Citroen C4 Aircross";

44. citroen[3] = "Citroen Nouveau Grand C4 Picasso";

45. citroen[4] = "Citroen Space Tourer Business Lounge";

46. }

48. static { // les types de carburants

49. carburants = new ArrayList<>();

50. carburants.add("Essence");

51. carburants.add("Diesel");

52. carburants.add("GNV"); // Gaz naturel pour véhicules

53. carburants.add("Hybride");

54. carburants.add("Biodiesel");

55. carburants.add("Electric");

56. }

58. static { // la consommation du litre/100km, on voit qu'elle est très élevée.

59. consommations = new ArrayList<>();

60. consommations.add(4.3f);

61. consommations.add(5.8f);

62. consommations.add(6.9f);

63. consommations.add(7.4f);

64. consommations.add(8.5f);

65. consommations.add(9.2f);

66. }

68. public static void main(String[] args) {

69. // commençons par afficher la liste des véhicules de la marque Peugeot

70. out.println(Arrays.asList(peugeot)); // [Peugeot Nouvelle SUV 3008, Peugeot Nouvelle SUV 5008, Peugeot Traveller/Expert Combi, Peugeot Boxer, Peugeot Tepee]

71. }

72. }

V-B - La méthode asList

V-B-1 - asList(E first, E[] rest)

Ajoutons un nouveau véhicule au début de notre liste en utilisant la méthode asList(E first, E[] rest), puis affichons cette liste de véhicules. On constate que le véhicule a été ajouté au tout début de la liste.

Méthode asList(E first, E[] rest)

1. final List<String> peugeotImmutableList = Lists.asList("Peugeot 107", peugeot);

2. peugeotImmutableList.forEach(System.out::println);

3. /*

4. Peugeot 107

5. Peugeot Nouvelle SUV 3008

6. Peugeot Nouvelle SUV 5008

7. Peugeot Traveller/Expert Combi

8. Peugeot Boxer

9. Peugeot Tepee

10. */

À noter que la liste est immuable, donc si par la suite vous essayez d'ajouter un autre véhicule une exception sera levée.

UnsupportedOperationException

1. peugeotImmutableList.add("Peugeot 107");

V-B-2 - asList(E first, E second, E[] rest)

Dans ce cas de figure, il est préférable d’utiliser la méthode asList(E first, E second, E[] rest).

Méthode asList(E first, E second, E[] rest)

1. final List<String> peugeotOtherImmutableList = Lists.asList("Peugeot 107", "Peugeot 108", peugeot);

2. peugeotOtherImmutableList.forEach(System.out::println);

3. /*

4. Peugeot 107

5. Peugeot 108

6. Peugeot Nouvelle SUV 3008

7. Peugeot Nouvelle SUV 5008

8. Peugeot Traveller/Expert Combi

9. Peugeot Boxer

10. Peugeot Tepee

11. */

V-C - La méthode cartesianProduct

Nous souhaitons maintenant récupérer le produit cartésien, c’est-à-dire l’ensemble des types de carburants disponibles pour les véhicules de la marque Peugeot. Par ailleurs cette méthode possède deux variantes, dont l’une prend en compte un argument variable de type List au lieu d’un unique élément de type List.

V-C-1 - cartesianProduct(List<? extends B> …lists)

Pour effectuer cette tâche de récupération des types de carburants, nous pouvons directement utiliser la deuxième variante de la méthode cartesianProduct(List<? extends B> …lists).

Méthode cartesianProduct(List<? extends List<? extends B>> ...lists)

1. List<List<String>> immutableCartesianProduct = Lists.cartesianProduct(peugeotImmutableList, carburants);

2. out.println(immutableCartesianProduct);

3. /*

4. [[Peugeot 108, Essence], [Peugeot 108, Diesel], [Peugeot 108, GNV], [Peugeot 108, Hybride], [Peugeot 108, Biodiesel], [Peugeot 108, Electric], [Peugeot Nouvelle SUV 3008, Essence], [Peugeot Nouvelle SUV 3008, Diesel], [Peugeot Nouvelle SUV 3008, GNV], [Peugeot Nouvelle SUV 3008, Hybride], [Peugeot Nouvelle SUV 3008, Biodiesel], [Peugeot Nouvelle SUV 3008, Electric], [Peugeot Nouvelle SUV 5008, Essence], [Peugeot Nouvelle SUV 5008, Diesel], [Peugeot Nouvelle SUV 5008, GNV], [Peugeot Nouvelle SUV 5008, Hybride], [Peugeot Nouvelle SUV 5008, Biodiesel], [Peugeot Nouvelle SUV 5008, Electric], [Peugeot Traveller/Expert Combi, Essence], [Peugeot Traveller/Expert Combi, Diesel], [Peugeot Traveller/Expert Combi, GNV], [Peugeot Traveller/Expert Combi, Hybride], [Peugeot Traveller/Expert Combi, Biodiesel], [Peugeot Traveller/Expert Combi, Electric], [Peugeot Boxer, Essence], [Peugeot Boxer, Diesel], [Peugeot Boxer, GNV], [Peugeot Boxer, Hybride], [Peugeot Boxer, Biodiesel], [Peugeot Boxer, Electric], [Peugeot Tepee, Essence], [Peugeot Tepee, Diesel], [Peugeot Tepee, GNV], [Peugeot Tepee, Hybride], [Peugeot Tepee, Biodiesel], [Peugeot Tepee, Electric]]

5. */

V-C-2 - cartesianProduct(List<? extends List<? extends B>> lists)

Soit nous pouvons utiliser une boucle pour parcourir les couples formés par le produit cartésien souhaité [véhicule, carburant].

Méthode cartesianProduct(List<? extends List<? extends B>> lists)

1. peugeotImmutableList.forEach(vehicule -> {

2. carburants.stream()

3. //.filter(carburant -> carburant.equals("Diesel")) // si on souhaite uniquement les véhicules "Diesel"

4. .map(carburant -> ImmutableList.of(vehicule, carburant))

5. .forEachOrdered(couple -> {

6. out.println(couple);

7. });

8. });

V-D - La méthode charactersOf

Maintenant nous souhaitons récupérer indépendamment chaque caractère d’une séquence de caractères d’un véhicule de la marque Peugeot.

V-D-1 - charactersOf(CharSequence sequence)

Méthode charactersOf(CharSequence sequence)

1. final CharSequence vehiculePeugeot4008 = "Peugeot 4008";

2. final List<Character> peugeot4008 = Lists.charactersOf(vehiculePeugeot4008);

3. out.println(peugeot4008); // [P, e, u, g, e, o, t,  , 4, 0, 0, 8]

Comme il ne s’agit que d’une vue de la séquence de caractères, cette dernière n’est accessible qu’en lecture seule. Par contre toute modification de la séquence de caractères initiale entraîne une modification de la vue.

UnsupportedOperationException

peugeot4008.remove(7); // UnsupportedOperationException

V-D-2 - charactersOf(String string)

Nous pouvons effectuer la même opération pour récupérer indépendamment chaque caractère d’une chaîne de caractères (d’un véhicule de la marque Peugeot) passée en paramètre. Par contre avec cette surcharge de la méthode charactersOf la liste renvoyée est directement immuable.

Méthode ImmutableList<Character> charactersOf(String string)

1. final ImmutableList<Character> vehiculePeugeot5008 = Lists.charactersOf("Peugeot 5008");

2. out.println(vehiculePeugeot5008); // [P, e, u, g, e, o, t,  , 5, 0, 0, 8]

V-E - La méthode newArrayList

Nous voulons cette fois-ci instancier un type ArrayList mutable comme nous le faisons lorsque nous utilisons cette structure de données. Mais avec l’API de Google Guava cela se fait différemment.

V-E-1 - newArrayList()

Méthode ArrayList<E> newArrayList()

1. final ArrayList<String> vehiculeMutableArrayList = Lists.newArrayList(); // une ArrayList mutable …

2. vehiculeMutableArrayList.add("Peugeot 207"); // à laquelle on ajoute un véhicule Peugeot

3. vehiculeMutableArrayList.add("Renault Velsatis"); // puis un véhicule Renault

4. vehiculeMutableArrayList.add("Citroen Saxo"); // puis un véhicule Renault

5. out.println(vehiculeMutableArrayList);

6. // [Peugeot 207, Renault Velsatis, Citroen Saxo]

V-E-2 - newArrayList(E… elements)

Cette méthode possède également une autre variante prenant comme paramètre une ellipse ; cette variante quant à elle nous permet de créer une ArrayList directement avec nos véhicules.

Méthode ArrayList<E> newArrayList(E... elements)

1. final ArrayList<String> toyotaMutableArrayList = Lists.newArrayList("Toyota Starlet", "Toyota Nouvelle Starlet"); // une liste mutable initialisée avec des véhicules Toyota

2. toyotaMutableArrayList.add("Toyota Nouvelle Starlet+"); // à laquelle on ajoute un nouveau véhicule Toyota

3. out.println(toyotaMutableArrayList);

4. // [Toyota Starlet, Toyota Nouvelle Starlet, Toyota Nouvelle Starlet+]

V-F - La méthode LinkedList

Idem avec pour un objet de type LinkedList :

Méthode LinkedList<E> newLinkedList()

1. final LinkedList<String> vehiculeMutableLinkedList = Lists.newLinkedList();  // une LinkedList mutable ...

2. vehiculeMutableLinkedList.add("Opel Zafira Life"); // à laquelle on ajoute un véhicule Opel

3. vehiculeMutableLinkedList.add("Citroen DS 7 Crossback"); // à laquelle on ajoute un véhicule Citroen

4. vehiculeMutableLinkedList.add("Renault Koléos"); // à laquelle on ajoute un véhicule Renault

5. out.println(vehiculeMutableLinkedList);

6. // [Opel Zafira Life, Citroen DS 7 Crossback, Renault Koléos]

Méthode LinkedList<E> newLinkedList(E... elements)

1. final LinkedList<String> opelMutableLinkedList = Lists.newLinkedList(Arrays.asList(opel));

2. opelMutableLinkedList.add("Opel Insignia Grand Sport");

3. out.println(opelMutableLinkedList);

4. // [Opel Calibra, Opel Ascona, Opel Corsa, Opel Alambra, Opel Meriva, Opel Insignia Grand Sport]

V-G - La méthode partition(List<T> list, int size)

L’API Google Guava nous permet également de partitionner (sectionner en sous-liste) notre liste de véhicules en choisissant la longueur de chacune des sous-listes. À noter que si tous les éléments ne peuvent pas être mis dans une sous-liste de longueur définie alors ils se retrouvent dans une sous-liste de longueur inférieure à celle définie à l’origine.

Méthode List<List<T>> partition(List<T> list, int size)

1. final List<List<String>> citroenListCarburants = Lists.partition(carburants, 5);

2. out.println(citroenListCarburants);

3. List<String> subList = Arrays.asList(citroen).subList(1, 3);

4. out.println(subList);

5. // [[Essence, Diesel, GNV, Hybride, Biodiesel], [Electric]]

6. // [Diesel, GNV]

V-H - La méthode reverse(List<T> list)

Désormais nous voulons restituer notre liste de véhicules dans l’ordre inverse d’insertion.

Méthode List<T> reverse(List<T> list)

1. out.println(Arrays.asList(toyota));

2. final List<String> toyotaReverseList = Lists.reverse(Arrays.asList(toyota));

3. out.println(toyotaReverseList);

4. // [Toyota Auris, Toyota Nouvelle Yaris, Toyota Aygo X , Toyota Prius+, Toyota Nouvelle C-HR]

5. // [Toyota Nouvelle C-HR, Toyota Prius+, Toyota Aygo X , Toyota Nouvelle Yaris, Toyota Auris]

V-I - La méthode transform(List<F> fromList, Function<? super F,? extends T> function)

Nous pouvons également transformer les éléments de notre liste en leur appliquant une fonction de « transformation » prédéfinie.

Nous avons trouvé un procédé permettant de gagner 40 % sur la consommation du litre au 100 km de nos véhicules.

Méthode <F,T> List<T> transform(List<F> fromList, Function<? super F,? extends T> function)

1. final Function<Float, Float> multiplier = t -> t * 0.6f;

2. // To use a java.util.function.Function in a context where a com.google.common.base.Function is needed, use function::apply.

3. // donc on peut utiliser directement la méthode de référence (sans argument) apply

4. final List<Float> diminuerConsommation = Lists.transform(consommations, multiplier::apply);

5. out.println(diminuerConsommation); // 1.5600000381469727

6. // ou directement avec la classe String de l'API Java

7. final List<String> carburantsToUppercase = Lists.transform(carburants, String::toUpperCase);

8. out.println(carburantsToUppercase);

9. // [2.5800002, 3.4800003, 4.1400003, 4.44, 5.1000004, 5.52]

10. // [ESSENCE, DIESEL, GNV, HYBRIDE, BIODIESEL, ELECTRIC]

VI - Conclusion

Nous avons fait le tour des méthodes utilitaires présentes dans la classe Lists. Comme vous avez pu le constater ces méthodes sont à la fois très simples d’utilisation et particulièrement efficaces. Je vous invite fortement à les implémenter dans vos projets, car elles vous apporteront des avantages sans précédent en termes de productivité.

Par ailleurs, je vous invite à créer un fil de discussion afin d’y laisser tous vos commentaires pour l’appréciation, voire pour l’amélioration des futurs tutoriels.

VII - Remerciements

Je tiens à remercier tout particulièrement claudeLELOUP pour la relecture orthographique de cet article.

Je tiens à remercier tout particulièrement la communauté developpez.com qui propose un forum d’entraide d’une richesse fortement appréciable. Je remercie également Mickael Baron qui donne de son temps pour s’occuper de la rubrique Java, RomeoBeni d’avoir écrit un ouvrage d’une rare simplicité pour apprendre à programmer en Java, mais aussi à tous les membres répondant aux questions des débutants en programmation dont je faisais partie il y a quelques années de cela. Merci à tous.