Predicate Commons Collections
L'utilisation de Predicate
, interface org.apache.commons.collections4.Predicate
, va permettre d'effectuer des recherches / filtres. Cette interface définie la fonction evaluate
qui retourne un boolean. Si la fonction retourne la valeur true
, le "Predicate" est validé.
Sommaire
Votre avis
Nobody voted on this yet
|
|
Implémentation
Dans le cadre de cette étude, une première implémentation est mise en place afin de tester la valeur de gender
sur les instances de People
.
package fr.ejn.tutorial.apache.commons.collections4.predicate;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Predicate;
/**
* Sample predicate used to filter people on gender.
*
* @author Etienne Jouvin
*
*/
public class GenderPredicate implements Predicate<People> {
private Gender expected;
/**
* Constructor with the expected gender for the predcate.
*
* @param expected Expected gender.
*/
public GenderPredicate(Gender expected) {
this.expected = expected;
}
/** {@inheritDoc} */
@Override
public boolean evaluate(People object) {
return null != object && expected.equals(object.getGender());
}
}
Le constructeur de ce Predicate
permet de spécifier la valeur de gender
qui est recherché.
La fonction evaluate
teste que l'argument n'est pas null, permettant de s'affranchir les NullPointerException
, et que la valeur de gender
est égale à celle fournie dans le constructeur. Attention, il n'y a aucun test sur la valeur fournie dans le constructeur, ce n'est pas totalement "null safe".
Recherche élément
Les implémentations de Predicate
permettent d'effectuer des recherches dans les listes. Cela est utile pour rechercher un élément dans une liste par exemple.
Avant la version 4 de la librairie, la classe CollectionUtils
était utilisée avec les fonctions find
. Mais elles sont passées en deprecated au profit de celles de la classe IterableUtils
.
Le test unitaire testFind
dans fr.ejn.tutorial.apache.commons.collections4.IterableUtilsTest
démontre la recherche de la première instance People
dont la valeur de gender
est MAN
@Test
public void testFind() {
People people = new People();
people.setName("Mother");
people.setGender(Gender.WOMAN);
people.setSurname("Jouvin");
List<People> peoples = new ArrayList<People>(3);
peoples.add(people);
// Add a null to test predicate on null.
peoples.add(null);
people = new People();
people.setName("Etienne");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
peoples.add(people);
people = new People();
people.setName("Etienne bis");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
peoples.add(people);
Predicate<People> predicate = new GenderPredicate(Gender.MAN);
// Search an instance.
People actual = IterableUtils.find(peoples, predicate);
Assert.assertNotNull("Validate result is not null", actual);
// Validate
Assert.assertEquals("Etienne", actual.getName());
Assert.assertEquals(Gender.MAN, actual.getGender());
Assert.assertEquals("Jouvin", actual.getSurname());
}
Dans un premier temps, une liste est créé avec différents éléments, dont plusieurs avec la valeur MAN
dans gender
. Puis la fonction find
de la classe IterableUtils
est exécutée sur la liste avec une instance du Predicate
. Ainsi, la première instance de People
trouvée est retournée, sans modification.
Le test unitaire valide le contenu de l'instance, en s'assurant que les données ne sont pas altérées.
Recherche index élément
Comme pour la recherche d'un élément, les Predicate
permettent de retrouver l'index du premier élément trouvé dans une liste.
Le test unitaire testIndexOf
dans fr.ejn.tutorial.apache.commons.collections4.IterableUtilsTest
démontre la recherche de l'index de la première instance People
dont la valeur de gender
est MAN
@Test
public void testIndexOf() {
People people = new People();
people.setName("Mother");
people.setGender(Gender.WOMAN);
people.setSurname("Jouvin");
List<People> peoples = new ArrayList<People>(3);
peoples.add(people);
// Add a null to test predicate on null.
peoples.add(null);
people = new People();
people.setName("Etienne");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
peoples.add(people);
people = new People();
people.setName("Etienne bis");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
peoples.add(people);
Predicate<People> predicate = new GenderPredicate(Gender.MAN);
// Search an instance.
int actual = IterableUtils.indexOf(peoples, predicate);
Assert.assertEquals(2, actual);
}
Dans un premier temps, une liste est créé avec différents éléments, dont plusieurs avec la valeur MAN
dans gender
. Puis la fonction indexOf
de la classe IterableUtils
est exécutée sur la liste avec une instance du Predicate
. Ainsi, la première instance de People
trouvée est retournée, sans modification.
Le test unitaire valide le contenu de l'instance, en s'assurant que les données ne sont pas altérées.
Extraction éléments
Les instances de Predicate
permettent d'identifier les éléments. Ils sont utilisés dans les fonctions de sélection des différents utilitaires. Un exemple est implémenté dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.predicate.GenderPredicateTest
.
package fr.ejn.tutorial.apache.commons.collections4.predicate;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.ListUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class GenderPredicateTest {
private GenderPredicate instance;
@Before
public void setUp() throws Exception {
instance = new GenderPredicate(Gender.MAN);
}
@Test
public void testEvaluate() {
People people = new People();
people.setName("Etienne");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
List<People> peoples = new ArrayList<People>(3);
peoples.add(people);
// Add a null to test predicate on null.
peoples.add(null);
people = new People();
people.setName("Mother");
people.setGender(Gender.WOMAN);
people.setSurname("Jouvin");
peoples.add(people);
List<People> actual = ListUtils.select(peoples, instance);
Assert.assertNotNull("Validate result is not null", actual);
Assert.assertEquals("Validate result size", actual.size(), 1);
people = actual.get(0);
Assert.assertEquals(people.getName(), "Etienne");
Assert.assertEquals(people.getGender(), Gender.MAN);
Assert.assertEquals(people.getSurname(), "Jouvin");
}
}
Dans un premier temps, une liste est créé avec différents éléments. Puis la fonction select
de la classe ListUtils
est exécutée sur la liste avec une instance du Predicate
. Ainsi, une nouvelle liste est créée avec uniquement les instances validées, soit celle dont la valeur de gender
est MAN
, sans modification.
Le test unitaire valide le contenu de la nouvelle liste, en s'assurant que les données ne sont pas altérées.
A noter que la classe ListUtils
est utilisée, afin d'obtenir une liste. La même fonction est disponible dans la classe CollectionUtils
, mais celle-ci retourne une collection, moins aisée pour les tests unitaires, même si techniquement c'est un ArrayList
.
Implémentations standard
L'interface Predicate
permet d'écrire sa propre logique fonctionnelle. Cependant de nombreuses implémentations sont déjà fournies pour des opérations basiques. Ces instances de Predicate
sont accessibles depuis la classe org.apache.commons.collections4.PredicateUtils
.
Suppression null
La fonction notNullPredicate
fournie une instance de la classe org.apache.commons.collections4.functors.NotNullPredicate
qui va permettre d'échapper les objets null. Son utilisation permet de filtrer tous les nulls d'une collection par exemple, comme démontré dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.NotNullPredicateTest
.
@Test
public void testInstance() {
// Build the list with some nulls.
List<String> source = Arrays.asList("Elt 1", null, "Elt 2", null, "Elt 3");
// Call the select with not null predicate.
// All null will be removed.
List<String> actual = ListUtils.select(source, PredicateUtils.notNullPredicate());
Assert.assertNotNull("Validate result is not null", actual);
Assert.assertEquals("Validate list size", 3, actual.size());
Assert.assertEquals("Elt 1", actual.get(0));
Assert.assertEquals("Elt 2", actual.get(1));
Assert.assertEquals("Elt 3", actual.get(2));
}
Dans ce test, les éléments d'une liste source sont extraits en supprimant tous les nulls.
Suppression doublons
Parfois, il est utile de supprimer les doublons d'une liste. La fonction uniquePredicate
fournie une instance de la classe org.apache.commons.collections4.functors.UniquePredicate
qui va permettre d'effectuer ce type de tri, comme démontré dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.UniquePredicateTest
.
@Test
public void testInstance() {
// Build the list with duplications.
List<String> source = Arrays.asList("Elt 1", "Elt 2", "Elt 3", "Elt 3", "Elt 2", "Elt 1");
// Call the select with unique predicate.
// All duplications will be removed.
List<String> actual = ListUtils.select(source, PredicateUtils.uniquePredicate());
Assert.assertNotNull("Validate result is not null", actual);
Assert.assertEquals("Validate list size", 3, actual.size());
Assert.assertEquals("Elt 1", actual.get(0));
Assert.assertEquals("Elt 2", actual.get(1));
Assert.assertEquals("Elt 3", actual.get(2));
}
Dans ce test, les éléments d'une liste source sont extraits en supprimant toutes les duplications volontairement introduites. Attention, le contrôle d'unicité est réalisé à l'aide d'une implémentation de java.util.Set
, et il faut bien faire attention à la condition d'évaluation d'égalité.
Null safe
Dans l'implémentation GenderPredicate
présentée en début d'article, un test de nullité est réalisé dans la fonction evaluate
. Ceci est une bonne pratique en tant que tel. Mais cela augmente la note de compléxité. La fonction nullIsFalsePredicate
fournie une instance de la classe org.apache.commons.collections4.functors.NullIsFalsePredicate
qui va permettre d'en capsuler une instance de Predicate. Lors de l'évaluation, celui-ci ne sera exécuté que si l'objet est non null. En utilisant ce système, il est donc possible de ne pas tester la nullité de l'objet, comme décrit dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.NullIsFalsePredicateTest
.
@Test
public void testInstance() {
People people = new People();
people.setName("Etienne");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
List<People> peoples = new ArrayList<People>(3);
peoples.add(people);
// Add a null to test predicate on null.
peoples.add(null);
people = new People();
people.setName("Mother");
people.setGender(Gender.WOMAN);
people.setSurname("Jouvin");
peoples.add(people);
// Create predicate.
Predicate<People> genderPredicate = new GenderUnsafePredicate(Gender.MAN);
// Use the NullIsFalse to check null before the predicate.
Predicate<People> instance = PredicateUtils.nullIsFalsePredicate(genderPredicate);
List<People> actual = ListUtils.select(peoples, instance);
Assert.assertNotNull("Validate result is not null", actual);
Assert.assertEquals("Validate result size", 1, actual.size());
people = actual.get(0);
Assert.assertEquals("Etienne", people.getName());
Assert.assertEquals(Gender.MAN, people.getGender());
Assert.assertEquals("Jouvin", people.getSurname());
}
Pour ce test, la nouvelle implémentation de Predicate
, classe fr.ejn.tutorial.apache.commons.collections4.predicate.GenderUnsafePredicate
a été écrite, inspirée de GenderPredicate
. Elle suppose que l'objet est non null.
package fr.ejn.tutorial.apache.commons.collections4.predicate;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Predicate;
/**
* Sample predicate used to filter people on gender, but without null safe evaluation.
*
* @author Etienne Jouvin
*
*/
public class GenderUnsafePredicate implements Predicate<People> {
private Gender expected;
/**
* Constructor with the expected gender for the predcate.
*
* @param expected Expected gender.
*/
public GenderUnsafePredicate(Gender expected) {
this.expected = expected;
}
/** {@inheritDoc} */
@Override
public boolean evaluate(People object) {
return expected.equals(object.getGender());
}
}
Multiple instances
L'exemple précédent a mis en évidence un moyen de filtrer les objets null, et sans le tester dans le code spécifique. Cependant, il existe une autre moyen d'effectuer ce filtre en utilisant une implémentation de Predicate
qui encapsule plusieurs sous instances. Lors de l'évaluation, elles seront exécutées une par une en s'arrêtant à la première évaluation en échec.
Cette implémentation, instance de la classe org.apache.commons.collections4.functors.AllPredicate
, est fournie par la fonction allPredicate
. Il est nécessaire de fournir la liste des Predicate
à encapsuler.
Donc afin de filtrer les objets nulls avant d'exécuter son propre Predicate
, il suffit de fournir une liste dont le premier élément est l'instance fournie par notNullPredicate
, décrite précédement. Le test unitaire fr.ejn.tutorial.apache.commons.collections4.AllPredicateTest
illustre ce cas d'utilisation.
@Test
public void testInstance() {
People people = new People();
people.setName("Etienne");
people.setGender(Gender.MAN);
people.setSurname("Jouvin");
List<People> peoples = new ArrayList<People>(3);
peoples.add(people);
// Add a null to test predicate on null.
peoples.add(null);
people = new People();
people.setName("Mother");
people.setGender(Gender.WOMAN);
people.setSurname("Jouvin");
peoples.add(people);
// Create two predicates.
Predicate<People> notNullPredicate = PredicateUtils.notNullPredicate();
Predicate<People> genderPredicate = new GenderUnsafePredicate(Gender.MAN);
// Use the all predicate.
// First one check not null, it will be safe.
Predicate<People> instance = PredicateUtils.allPredicate(Arrays
.asList(notNullPredicate, genderPredicate));
List<People> actual = ListUtils.select(peoples, instance);
Assert.assertNotNull("Validate result is not null", actual);
Assert.assertEquals("Validate result size", 1, actual.size());
people = actual.get(0);
Assert.assertEquals("Etienne", people.getName());
Assert.assertEquals(Gender.MAN, people.getGender());
Assert.assertEquals("Jouvin", people.getSurname());
}
L'utilisation de ce type de Predicate
va donc permettre d'en enchainer une multitude. Le même type de logique est implémentée dans les instances retournées par les fonctions suivantes.
-
andPredicate
, encapsule deux instances qui doivent valider l'évaluation. -
anyPredicate
, s'arrête à la première évaluation en succès. -
orPredicate
, s'arrêt quand au moins une évaluation est en succès.