Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Prochaine sessions inter-entreprises : 13-16 février 2018
Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Guava par l'exemple (1/3) : les classes utilitaires

Guava est une librairie Java éditée par Google, qui fournit de nombreuses fonctionnalités qui auraient dû se trouver dans le JDK.

Cette série d'articles n'a pas pour vocation de détailler toutes les classes de la librairie, mais d'en présenter une sélection facilement utilisables sur vos projets.
Chaque classe présentée est assortie d'un exemple sous la forme d'un test unitaire mettant en évidence son fonctionnement.

Cet article fait partie d'une série :

Joiner / Splitter

Joiner et Splitter sont deux classes utilitaires facilitant la transformation bidirectionnelle entre des ensembles d'objets et des chaînes de caractères.

Joiner

Joiner prend un ensemble d'objets (Collection, Iterable, varargs...) et les joint grâce à un séparateur. Cette fonction est très utile pour générer du CSV par exemple (val1;val2;val3...)

L'API de type builder permet de spécifier le caractère de séparation, et la façon de gérer les valeurs nulles : les ignorer (skipNulls) ou les remplacer (useForNull).

Il existe une variante fonctionnant avec des Maps. Dans ce cas, il faut également préciser le caractère qui sépare les clés des valeurs (k1=v1, k2=v2...)

@Test
public void joinVarArgs() {
	String result = null;
 
	Joiner joinerOnString = Joiner.on(", ");
	result = joinerOnString.join("Hello", "World");
	assertEquals("Hello, World", result);
 
	Joiner joinerOnChar = Joiner.on(' ');
	result = joinerOnChar.join("Hello", "World");
	assertEquals("Hello World", result);
}
 
@Test
public void joinIterable() {
	String result = null;
	List<String> daltons = Arrays.asList("Joe", "Jack", "William", "Averell");
 
	Joiner joiner = Joiner.on(", ");
	result = joiner.join(daltons);
	assertEquals("Joe, Jack, William, Averell", result);
}
 
@Test
public void joinNulls() {
	String result = null;
	List<String> daltons = Arrays.asList("Joe", null, "Jack", null, "William", null, "Averell");
 
	Joiner joinerSkippingNulls = Joiner.on(", ").skipNulls();
	result = joinerSkippingNulls.join(daltons);
	assertEquals("Joe, Jack, William, Averell", result);
 
	Joiner joinerReplacingNulls = Joiner.on(", ").useForNull("<null>");
	result = joinerReplacingNulls.join(daltons);
	assertEquals("Joe, <null>, Jack, <null>, William, <null>, Averell", result);
}
 
@Test
public void joinMap() {
	String result = null;
	Map<String, Integer> daltonsSize = new LinkedHashMap<String, Integer>();
	daltonsSize.put("Joe",      1);
	daltonsSize.put("Jack",     2);
	daltonsSize.put("William",  3);
	daltonsSize.put("Averell",  4);
 
	Joiner.MapJoiner mapJoiner = Joiner.on(", ").withKeyValueSeparator("=");
	result = mapJoiner.join(daltonsSize);
	assertEquals("Joe=1, Jack=2, William=3, Averell=4", result);
}

Splitter

Splitter fait l'inverse de Joiner : il découpe une chaîne de caractères pour en extraire une collection de valeurs. Il peut être très pratique pour parser des fichiers CSV.

Splitter peut découper selon un délimiteur fixe (type String) ou selon un Pattern (regex). Il peut également être paramétré pour supprimer les blancs autour des valeurs (trimResults) et/ou pour ignorer les valeurs vides (omitEmptyStrings).

@Test
public void splitByString() {
	String daltons = "Joe, Jack, William, Averell";
	String delimiter = ", ";
 
	Splitter splitterOnString = Splitter.on(delimiter);
	Iterable<String> split = splitterOnString.split(daltons);
 
	Iterator<String> iterator = split.iterator();
	assertEquals("Joe", iterator.next());
	assertEquals("Jack", iterator.next());
	assertEquals("William", iterator.next());
	assertEquals("Averell", iterator.next());
}
 
@Test
public void splitByPattern() {
	String daltons = "Joe, Jack, William, Averell";
	String delimiter = ",\\s+";
 
	Splitter splitterOnString = Splitter.onPattern(delimiter);
	Iterable<String> split = splitterOnString.split(daltons);
 
	Iterator<String> iterator = split.iterator();
	assertEquals("Joe", iterator.next());
	assertEquals("Jack", iterator.next());
	assertEquals("William", iterator.next());
	assertEquals("Averell", iterator.next());
}
 
@Test
public void splitAndRemoveEmptyResults() {
	String daltons = "Joe, , Jack, , William, , Averell";
	String delimiter = ", ";
 
	Splitter splitterOnString = Splitter.on(delimiter).trimResults().omitEmptyStrings();
	Iterable<String> split = splitterOnString.split(daltons);
 
	Iterator<String> iterator = split.iterator();
	assertEquals("Joe", iterator.next());
	assertEquals("Jack", iterator.next());
	assertEquals("William", iterator.next());
	assertEquals("Averell", iterator.next());
}


Objects

Objects contient deux méthodes utiles au quotidien, et qui gèrent correctement les null :

  • equal() qui permet de tester l'égalité de deux instances ;
  • toStringHelper, qui permet d'implémenter toString facilement, grâce à une API fluide.
private static class Pojo {
 
	private final String aString;
	private final int anInt;
	private final Object anObject;
 
	private Pojo(String aString, int anInt, Object anObject) {
		this.aString = aString;
		this.anInt = anInt;
		this.anObject = anObject;
	}
 
	@Override
	public String toString() {
		return Objects.toStringHelper(this)
				.add("aString", aString)
				.add("anInt", anInt)
				.add("anObject", anObject)
				.toString();
	}
 
}
 
@Test
public void equal() {
	Pojo pojo = new Pojo("foo", 42, null);
	assertTrue(Objects.equal(null, null));
	assertFalse(Objects.equal(pojo, null));
	assertFalse(Objects.equal(null, pojo));
	assertTrue(Objects.equal(pojo, pojo));
	assertFalse(Objects.equal(pojo, new Object()));
}
 
@Test
public void toStringHelper() {
	assertEquals("Pojo{aString=foo, anInt=42, anObject=null}", new Pojo("foo", 42, null).toString());
	assertEquals("Pojo{aString=foo, anInt=42, anObject=bar}", new Pojo("foo", 42, "bar").toString());
}


Preconditions

La classe Preconditions propose deux méthodes facilitant la vérification des paramètres passés en entrée des méthodes.

  • checkNotNull vérifie la non-nullité du paramètre, et lance un NullPointerException si besoin.
  • checkArgument vérifie la validité du paramètre grâce à une expression de validation, et lance un IllegalArgumentException si besoin.

Les deux méthodes peuvent également prendre en paramètre une chaîne de caractères (simple ou à formater), qui sera utilisée comme message si une exception est lancée.

Notes à propos du débat IllegalArgumentException vs NullPointerException : certaines personnes n'aiment pas lancer NullPointerException "manuellement" et considèrent que c'est une mauvaise pratique. Pourtant, tout le monde s'accorde sur le fait qu'il faut toujours lancer l'exception la plus précise et décrivant le mieux le cas d'erreur rencontré. Dans le cas où l'on a fourni par mégarde une référence nulle à votre méthode, c'est bien le NPE qui correspond le mieux, et qui doit donc être lancé. L'intégralité du code du JDK est basé sur cette règle, et le code de Google également. Alors pourquoi pas le vôtre ?

private void methodWithNotNullParam(String param) {
	// Preconditions.checkNotNull(param);
	// Preconditions.checkNotNull(param, "Parameter 'param' must not be null");
	Preconditions.checkNotNull(param, "Parameter '%s' must not be null", "param");
}
 
private void methodWithValidParam(String param) {
	// Preconditions.checkArgument(param.trim().length() > 0);
	// Preconditions.checkArgument(param.trim().length() > 0, "Parameter 'param' must not be empty");
	Preconditions.checkArgument(param.trim().length() > 0, "Parameter '%s' must not be empty", "param");
}
 
@Test
public void testMethodWithNotNullParam() {
	try {
		methodWithNotNullParam(null);
		fail("Target method should have thrown an exception");
	} catch (NullPointerException npe) {
		assertEquals("Parameter 'param' must not be null", npe.getMessage());
	}
}
 
@Test
public void testMethodWithValidParam() {
	try {
		methodWithValidParam("");
		fail("Target method should have thrown an exception");
	} catch (IllegalArgumentException iae) {
		assertEquals("Parameter 'param' must not be empty", iae.getMessage());
	}
}


Strings

La classe utilitaire Strings fournit une poignée de méthodes qui manquent dans la classe String : - pour la gestion des chaînes nulles ou vides : emptyToNull, nullToEmpty, isNullOrEmpty - pour ajouter du padding : padStart et padEnd - pour répéter une chaîne : repeat

@Test
public void nullString() {
	String nullString = null;
	String emptyString = "";
	String someString = "Hello World";
 
	assertEquals(null, Strings.emptyToNull(nullString));
	assertEquals(null, Strings.emptyToNull(emptyString));
	assertEquals(someString, Strings.emptyToNull(someString));
 
	assertEquals("", Strings.nullToEmpty(nullString));
	assertEquals("", Strings.nullToEmpty(emptyString));
	assertEquals(someString, Strings.nullToEmpty(someString));
 
	assertTrue(Strings.isNullOrEmpty(nullString));
	assertTrue(Strings.isNullOrEmpty(emptyString));
	assertFalse(Strings.isNullOrEmpty(someString));
}
 
@Test
public void padding() {
	String greeting = "Hello World";
	char padding = '.';
 
	assertEquals(greeting, Strings.padStart(greeting, -1, padding));
	assertEquals(greeting, Strings.padStart(greeting, 0, padding));
	assertEquals("...."+greeting, Strings.padStart(greeting, 15, padding));
 
	assertEquals(greeting, Strings.padEnd(greeting, -1, padding));
	assertEquals(greeting, Strings.padEnd(greeting, 0, padding));
	assertEquals(greeting+"....", Strings.padEnd(greeting, 15, padding));
}
 
@Test
public void repeat() {
	String laugh = "ha";
 
	assertEquals("", Strings.repeat(laugh, 0));
	assertEquals("ha", Strings.repeat(laugh, 1));
	assertEquals("hahaha", Strings.repeat(laugh, 3));
}

Conclusion

Les classes utilitaires de Guava fournissent des méthodes qui mériteraient de figurer dans le JDK, et qui sont actuellement réimplémentées "à la main" dans la majorité des projets.

En utilisant Guava, vous bénéficiez d'un code robuste concentrant tout le savoir-faire de Google, et vous pouvez vous concentrer sur l'écriture de code à valeur ajoutée.

Dans le prochain billet, je vous présenterai les classes et concepts liés à la gestion des Collections. Stay tuned !


Commentaires

1. Le lundi 26 septembre 2011, 15:23 par Al Wad

Merci Maitre

2. Le mardi 27 septembre 2011, 12:48 par HollyDays

"qui sont actuellement réimplémentées "à la main" dans la majorité des projets."

Yep, je confirme. En plus, 4 fois sur 5, elles sont mal réimplémentées... Conditions aux limites non prévues, surconsommation mémoire ou processeur, voire, parfois, véritables bugs, j'en passe et des meilleures !

3. Le lundi 3 octobre 2011, 09:58 par Nicolas

Bel article qui fait un bon tour d'horizon des diverses petites choses sympa dans Guava.
Par contre, un petit bémol, il manque une des fonctionnalités dont je me sers souvent : la définition facile des méthodes equals et hasCode dans Objects mais aussi les ComparisonChain pour définir les méthodes de comparaisons. Un des avantages de ces dernières étant d'être GwtCompatible.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.