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.
2.
3.
4.
5.
6.
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>
com.google.guava</groupId>
<artifactId>
guava</artifactId>
<version>
30.1-jre</version>
</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▲
2.
3.
4.
5.
6.
7.
8.
import
com.google.common.collect.ImmutableList;
import
com.google.common.collect.Lists;
import
static
java.lang.System.out;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.LinkedList;
import
java.util.List;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
public
final
class
Application {
private
final
static
int
NB_MARQUES_VEHICULES =
5
;
private
final
static
String[] peugeot;
private
final
static
String[] toyota;
private
final
static
String[] opel;
private
final
static
String[] renault;
private
final
static
String[] citroen;
private
final
static
List<
String>
carburants; // types de carburants
private
final
static
List<
Float>
consommations;
static
{
// les marques des véhicules
peugeot =
new
String[NB_MARQUES_VEHICULES];
peugeot[0
] =
"Peugeot Nouvelle SUV 3008"
;
peugeot[1
] =
"Peugeot Nouvelle SUV 5008"
;
peugeot[2
] =
"Peugeot Traveller/Expert Combi"
;
peugeot[3
] =
"Peugeot Boxer"
;
peugeot[4
] =
"Peugeot Tepee"
;
toyota =
new
String[NB_MARQUES_VEHICULES];
toyota[0
] =
"Toyota Auris"
;
toyota[1
] =
"Toyota Nouvelle Yaris"
;
toyota[2
] =
"Toyota Aygo X "
;
toyota[3
] =
"Toyota Prius+"
;
toyota[4
] =
"Toyota Nouvelle C-HR"
;
opel =
new
String[NB_MARQUES_VEHICULES];
opel[0
] =
"Opel Calibra"
;
opel[1
] =
"Opel Ascona"
;
opel[2
] =
"Opel Corsa"
;
opel[3
] =
"Opel Alambra"
;
opel[4
] =
"Opel Meriva"
;
renault =
new
String[NB_MARQUES_VEHICULES];
renault[0
] =
"Renault Kangoo VP"
;
renault[1
] =
"Renault Nouveau Grand Scenic"
;
renault[2
] =
"Renault Alaskan"
;
renault[3
] =
"Talisman Estate"
;
renault[4
] =
"Renault Safrane"
;
citroen =
new
String[NB_MARQUES_VEHICULES];
citroen[0
] =
"Citroen Nouvelle C-Elysée"
;
citroen[1
] =
"Citroen Nemo Multispace"
;
citroen[2
] =
"Citroen C4 Aircross"
;
citroen[3
] =
"Citroen Nouveau Grand C4 Picasso"
;
citroen[4
] =
"Citroen Space Tourer Business Lounge"
;
}
static
{
// les types de carburants
carburants =
new
ArrayList<>(
);
carburants.add
(
"Essence"
);
carburants.add
(
"Diesel"
);
carburants.add
(
"GNV"
); // Gaz naturel pour véhicules
carburants.add
(
"Hybride"
);
carburants.add
(
"Biodiesel"
);
carburants.add
(
"Electric"
);
}
static
{
// la consommation du litre/100km, on voit qu'elle est très élevée.
consommations =
new
ArrayList<>(
);
consommations.add
(
4.3
f);
consommations.add
(
5.8
f);
consommations.add
(
6.9
f);
consommations.add
(
7.4
f);
consommations.add
(
8.5
f);
consommations.add
(
9.2
f);
}
public
static
void
main
(
String[] args) {
// commençons par afficher la liste des véhicules de la marque Peugeot
out.println
(
Arrays.asList
(
peugeot)); // [Peugeot Nouvelle SUV 3008, Peugeot Nouvelle SUV 5008, Peugeot Traveller/Expert Combi, Peugeot Boxer, Peugeot Tepee]
}
}
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
final
List<
String>
peugeotImmutableList =
Lists.asList
(
"Peugeot 107"
, peugeot);
peugeotImmutableList.forEach
(
System.out::println);
/*
Peugeot 107
Peugeot Nouvelle SUV 3008
Peugeot Nouvelle SUV 5008
Peugeot Traveller/Expert Combi
Peugeot Boxer
Peugeot Tepee
*/
À noter que la liste est immuable, donc si par la suite vous essayez d'ajouter un autre véhicule une exception sera levée.
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).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
final
List<
String>
peugeotOtherImmutableList =
Lists.asList
(
"Peugeot 107"
, "Peugeot 108"
, peugeot);
peugeotOtherImmutableList.forEach
(
System.out::println);
/*
Peugeot 107
Peugeot 108
Peugeot Nouvelle SUV 3008
Peugeot Nouvelle SUV 5008
Peugeot Traveller/Expert Combi
Peugeot Boxer
Peugeot Tepee
*/
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).
2.
3.
4.
5.
List<
List<
String>>
immutableCartesianProduct =
Lists.cartesianProduct
(
peugeotImmutableList, carburants);
out.println
(
immutableCartesianProduct);
/*
[[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]]
*/
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].
2.
3.
4.
5.
6.
7.
8.
peugeotImmutableList.forEach
(
vehicule ->
{
carburants.stream
(
)
//.filter(carburant -> carburant.equals("Diesel")) // si on souhaite uniquement les véhicules "Diesel"
.map
(
carburant ->
ImmutableList.of
(
vehicule, carburant))
.forEachOrdered
(
couple ->
{
out.println
(
couple);
}
);
}
);
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)▲
2.
3.
final
CharSequence vehiculePeugeot4008 =
"Peugeot 4008"
;
final
List<
Character>
peugeot4008 =
Lists.charactersOf
(
vehiculePeugeot4008);
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.
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.
2.
final
ImmutableList<
Character>
vehiculePeugeot5008 =
Lists.charactersOf
(
"Peugeot 5008"
);
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()▲
2.
3.
4.
5.
6.
final
ArrayList<
String>
vehiculeMutableArrayList =
Lists.newArrayList
(
); // une ArrayList mutable …
vehiculeMutableArrayList.add
(
"Peugeot 207"
); // à laquelle on ajoute un véhicule Peugeot
vehiculeMutableArrayList.add
(
"Renault Velsatis"
); // puis un véhicule Renault
vehiculeMutableArrayList.add
(
"Citroen Saxo"
); // puis un véhicule Renault
out.println
(
vehiculeMutableArrayList);
// [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.
2.
3.
4.
final
ArrayList<
String>
toyotaMutableArrayList =
Lists.newArrayList
(
"Toyota Starlet"
, "Toyota Nouvelle Starlet"
); // une liste mutable initialisée avec des véhicules Toyota
toyotaMutableArrayList.add
(
"Toyota Nouvelle Starlet+"
); // à laquelle on ajoute un nouveau véhicule Toyota
out.println
(
toyotaMutableArrayList);
// [Toyota Starlet, Toyota Nouvelle Starlet, Toyota Nouvelle Starlet+]
V-F. La méthode LinkedList▲
Idem avec pour un objet de type LinkedList :
2.
3.
4.
5.
6.
final
LinkedList<
String>
vehiculeMutableLinkedList =
Lists.newLinkedList
(
); // une LinkedList mutable ...
vehiculeMutableLinkedList.add
(
"Opel Zafira Life"
); // à laquelle on ajoute un véhicule Opel
vehiculeMutableLinkedList.add
(
"Citroen DS 7 Crossback"
); // à laquelle on ajoute un véhicule Citroen
vehiculeMutableLinkedList.add
(
"Renault Koléos"
); // à laquelle on ajoute un véhicule Renault
out.println
(
vehiculeMutableLinkedList);
// [Opel Zafira Life, Citroen DS 7 Crossback, Renault Koléos]
2.
3.
4.
final
LinkedList<
String>
opelMutableLinkedList =
Lists.newLinkedList
(
Arrays.asList
(
opel));
opelMutableLinkedList.add
(
"Opel Insignia Grand Sport"
);
out.println
(
opelMutableLinkedList);
// [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.
2.
3.
4.
5.
6.
final
List<
List<
String>>
citroenListCarburants =
Lists.partition
(
carburants, 5
);
out.println
(
citroenListCarburants);
List<
String>
subList =
Arrays.asList
(
citroen).subList
(
1
, 3
);
out.println
(
subList);
// [[Essence, Diesel, GNV, Hybride, Biodiesel], [Electric]]
// [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.
2.
3.
4.
5.
out.println
(
Arrays.asList
(
toyota));
final
List<
String>
toyotaReverseList =
Lists.reverse
(
Arrays.asList
(
toyota));
out.println
(
toyotaReverseList);
// [Toyota Auris, Toyota Nouvelle Yaris, Toyota Aygo X , Toyota Prius+, Toyota Nouvelle C-HR]
// [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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
final
Function<
Float, Float>
multiplier =
t ->
t *
0.6
f;
// To use a java.util.function.Function in a context where a com.google.common.base.Function is needed, use function::apply.
// donc on peut utiliser directement la méthode de référence (sans argument) apply
final
List<
Float>
diminuerConsommation =
Lists.transform
(
consommations, multiplier::apply);
out.println
(
diminuerConsommation); // 1.5600000381469727
// ou directement avec la classe String de l'API Java
final
List<
String>
carburantsToUppercase =
Lists.transform
(
carburants, String::toUpperCase);
out.println
(
carburantsToUppercase);
// [2.5800002, 3.4800003, 4.1400003, 4.44, 5.1000004, 5.52]
// [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.