Predicate Commons Collections

De EjnTricks

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é.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Java format icon.png 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".


File-find-icon.png 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.


File-find-icon.png 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.


Copy-icon.png 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.


Icon-memory.png 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.

File-find-icon.png 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.

Vues-icon.png 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é.

Plugin-icon.png 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());
  }

}

Multiples-icon.png 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.