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 !

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :