mai
2012
Dans mon code, je me retrouve tout le temps à devoir fabriquer une représentation des contenus de mes listes (et collections de manière générale) sous forme de String. A chaque fois, c’est le mal de tête assuré car il y a toujours des cas tordus à prendre en compte. Heureusement, Guava arrive avec l’objet « Joiner » pour simplifier toutes les manipulation usuelles.
J’avais déjà présenté les Splitter dans un autre billet que je vous encourage à lire. On peut dire que le Joiner est le « pendant » du Splitter.
Pour commencer avec un cas simple, prenons une liste contenant les prénoms de mes chiens de bande dessinée préférés :
1 | List<String> chiens = newArrayList("Milou", "Idefix", "Rintintin"); |
Bien entendu, je fais l’import static de newArrayList() :
1 | import static com.google.common.collect.Lists.newArrayList; |
Ce que je souhaite faire, c’est transformer cette liste en String, avec les prénoms séparés par une virgule. En Java classique, je peux faire ça de la manière (lourde) suivante :
1 2 3 4 5 6 | StringBuilder sb = new StringBuilder(); for(String prenom : chiens) { sb.append(prenom); sb.append(", "); } final String result = sb.toString(); |
Ce qui donne le résultat « Milou, Idefix, Rintintin, » avec un séparateur en trop à la fin. Il faut donc gérer ce cas :
1 2 3 4 5 6 7 8 9 10 11 12 | boolean first = true; StringBuilder sb = new StringBuilder(); for(String prenom : chiens) { if(first) { first = false; } else { sb.append(", "); } sb.append(prenom); } |
Le code complet :
1 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 | @Test public void testJoinJavaClassique1() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", "Rintintin"); final String resultatAttendu = "Milou, Idefix, Rintintin, "; // Act final StringBuilder sb = new StringBuilder(); for (String prenom : chiens) { sb.append(prenom); sb.append(", "); } final String result = sb.toString(); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } @Test public void testJoinJavaClassique2() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", "Rintintin"); final String resultatAttendu = "Milou, Idefix, Rintintin"; // Act boolean first = true; final StringBuilder sb = new StringBuilder(); for (String prenom : chiens) { if (first) { first = false; } else { sb.append(", "); } sb.append(prenom); } final String result = sb.toString(); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
On sent que ça commence à faire mal sous les cheveux, alors qu’on a à peine commencé.
Avec Guava, c’est beaucoup plus simple. Il suffit d’utiliser un Joiner :
1 | String result = Joiner.on(", ").join(chiens); |
Code entier :
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void testSimpleJoiner() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", "Rintintin"); final String resultatAttendu = "Milou, Idefix, Rintintin"; // Act final String result = Joiner.on(", ").join(chiens); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
Regardons maintenant ce qui se passe lorsqu’on a un élément « null » dans la liste :
1 | List<String> chiens = newArrayList("Milou", "Idefix", null, "Rintintin"); |
Avec la méthode classique, on se mange une jolie NPE dont on doit donc se protéger :
1 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 | @Test public void testJoinJavaClassique3() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", null, "Rintintin"); final String resultatAttendu = "Milou, Idefix, Rintintin"; // Act boolean first = true; final StringBuilder sb = new StringBuilder(); for (String prenom : chiens) { if (prenom == null) { continue; } if (first) { first = false; } else { sb.append(", "); } sb.append(prenom); } final String result = sb.toString(); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
Oh la la, vive l’aspirine…
Et avec Guava, ça ne passe pas non plus tout seul :
1 2 3 4 5 6 7 8 | @Test(expected = NullPointerException.class) public void testNullJoiner() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", null, "Rintintin"); // Act final String result = Joiner.on(", ").join(chiens); // --> NPE } |
Comme je le disais au Paris JUG (vidéo sur Parleys), Google n’aime pas les nuls. Il faut chaîner un petit appel de méthodes :
1 | String result = Joiner.on(", ").skipNulls().join(chiens); |
Code du test en entier :
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void testSkipNullJoiner() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", null, "Rintintin"); final String resultatAttendu = "Milou, Idefix, Rintintin"; // Act final String result = Joiner.on(", ").skipNulls().join(chiens); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
Cela dit, la plupart du temps, ce qu’on souhaite, ce n’est pas tellement de sauter les valeurs nulles ou vides, mais de les remplacer par une séquence spéciale. En Java standard, on sent que l’algorithme va devenir vraiment casse pied. Je ne vais même pas le présenter ici, tellement il est simple avec Guava :
1 | String result = Joiner.on(", ").useForNull("Dog").join(chiens); |
Code du test en entier :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test public void testUseForNullJoiner() { // Arrange final List<String> chiens = newArrayList("Milou", "Idefix", null, "Rintintin"); final String resultatAttendu = "Milou, Idefix, Dog, Rintintin"; final String remplacement ="Dog"; // Act final String result = Joiner.on(", ").useForNull(remplacement).join(chiens); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
Ici, c’est incontestable que Guava apporte un vrai plus.
Comme c’est souvent le cas dans cette bibliothèque, on peut faire avec les Map ce qu’on peut faire avec les List. A titre d’illustration, prenons les âges de nos chiens :
1 | Map<String, Integer> ages = ImmutableMap.of("Milou", 3, "Idefix", 5, "Rintintin", 2); |
Pour les représenter sous forme de String, on peut utiliser un Joiner :
1 | String result = Joiner.on(", ").withKeyValueSeparator(" -> ").join(ages); |
Code du test en entier :
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void testMapJoiner() { // Arrange final Map<String, Integer> ages = ImmutableMap.of("Milou", 3, "Idefix", 5, "Rintintin", 2); final String resultatAttendu = "Milou -> 3, Idefix -> 5, Rintintin -> 2"; // Act final String result = Joiner.on(", ").withKeyValueSeparator(" -> ").join(ages); System.out.println(result); // Assert Assert.assertEquals(resultatAttendu, result); } |
Que la vie du développeur est plus simple avec Guava…
Commentaires récents
- Le Stop watch de Guava dans
- Le Stop watch de Guava dans
- Le Stop watch de Guava dans