Unicité dans liste Commons Collections

De EjnTricks
Révision de 4 août 2017 à 13:44 par Etienne (discussion | contributions) (Page créée avec « Cet article va présenter comment construire une liste avec contrôle d'unicité basé sur des champs spécifiques des objets d'une liste d'origine. Tout le code prése... »)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

Cet article va présenter comment construire une liste avec contrôle d'unicité basé sur des champs spécifiques des objets d'une liste d'origine.

Tout le code présenté, dans le test unitaire fr.ejn.tutorial.apache.commons.collections4.ComparatorUtilsTest, est disponible ici : http://www.jouvinio.net/svn/study/trunk/apacheCommons


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Study icon.png Objectif

La construction de liste, avec unicité des éléments, s'effectue simplement avec les objets standard Java implémentant l'interface Set. Cependant, cela fonctionne avec le résultat de comparaison de la méthode equals.

Or, il peut être nécessaire de vouloir comparer les champs sur des propriétés particulières où via une fonction entièrement spécifique. Pour cela, la classe TreeSet propose un constructeur acceptant une instance de Comparator.

Ecrire un comparateur n'est pas spécialement compliqué, mais il faut tenir compte de certains contraintes comme la réciprocité.

Cet article présente donc l'utilisation de la classe ComparatorUtils et d'une transformation des données à l'aide d'une implémentation de Transformer.


Java format icon.png Implémentation

Pour cet exemple, un simple POJO est utilisé pour l'ajout dans une liste d'origine.

package fr.ejn.tutorial.datas;

/**
 * People pojo.
 *
 * @author Etienne Jouvin
 *
 */
public class People {

  private Gender gender;
  private String name;
  private String surname;

  /**
   * Get the gender.
   *
   * @return the gender.
   */
  public Gender getGender() {
    return gender;
  }

  /**
   * Get the name.
   *
   * @return the name.
   */
  public String getName() {
    return name;
  }

  /**
   * Get the surname.
   *
   * @return the surname.
   */
  public String getSurname() {
    return surname;
  }

  /**
   * Set the gender.
   *
   * @param gender the gender to set.
   */
  public void setGender(Gender gender) {
    this.gender = gender;
  }

  /**
   * Set the name.
   *
   * @param name the name to set.
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Set the surname.
   *
   * @param surname the surname to set.
   */
  public void setSurname(String surname) {
    this.surname = surname;
  }

}

L'objectif est de pouvoir construire une liste d'instance avec unicité sur la combinaison name et surname.


Update icon.png Transformation

La classe People ne présente pas de fonction pour obtenir la combinaison name et surname. Afin de ne pas le modifier, un Transformer est créé avec le code suivant.

package fr.ejn.tutorial.apache.commons.collections4.transformer;

import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.lang3.StringUtils;

/**
 * Sample transformer used to buld the people name.
 *
 * @author Etienne Jouvin
 *
 */
public class PeopleNameTransformer implements Transformer<People, String> {

  /** {@inheritDoc} */
  @Override
  public String transform(People input) {
    return null == input ? StringUtils.EMPTY
        : String.format("%s %s", input.getName(), input.getSurname());
  }

}

A noter que cette implémentation n'est pas nullsafe.


File-find-icon.png Comparaison

A l'aide du Transformer, il est donc possible de construire la valeur utilisée pour la comparaison.

Grace à la classe ComparatorUtils, il est ensuite possible d'utiliser ce Transformer comme un comparator. La fonction transformedComparator accepte deux arguments, le premier (pouvant être null) étant l'implémentation de Comparator à utiliser, le second le Transformer.

Dans le cadre de cet exemple, la comparaison s'effectue sur une chaîne de caractères et l'utilisation de la fonction compareTo suffit. L'objectif étant de ne pas l'écrire, un null sera utilisé comme argument. Ainsi, l'instance ComparatorUtils.NATURAL_COMPARATOR sera utilisée. Cette dernière ne faisant que retourner le résultat de la fonction compareTo des arguments.

La construction du comparateur s'effectue ainsi.

    Comparator<People> comparator = ComparatorUtils
        .transformedComparator(null, new PeopleNameTransformer());

L'instance est alors utilisée lors de l'appel au constructeur de TreeSet.

    Set<People> actual = new TreeSet<People>(comparator);

Le test unitaire suivant démmontre qu'une liste originale de quatre éléments, deux fois la même combinaison de name et surname mais avec un gender différent, donne une liste avec uniquement une instance par combinaison, seul le premier élément ajouté est stocké dans la cible.

package fr.ejn.tutorial.apache.commons.collections4;

import static org.assertj.core.api.Assertions.assertThat;

import fr.ejn.tutorial.apache.commons.collections4.transformer.PeopleNameTransformer;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ComparatorUtils;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author Etienne Jouvin
 *
 */
public class ComparatorUtilsTest {

  private People buildPeople(String name, String surname, Gender gender) {
    People res = new People();

    res.setName(name);
    res.setSurname(surname);
    res.setGender(gender);

    return res;
  }

  @Test
  public void testUniqueLisSpecificComparator() {
    List<People> input = new ArrayList<People>();
    input.add(this.buildPeople("name 1", "surname 1", Gender.MAN));
    input.add(this.buildPeople("name 1", "surname 1", Gender.WOMAN));
    input.add(this.buildPeople("name 2", "surname 2", Gender.MAN));
    input.add(this.buildPeople("name 2", "surname 2", Gender.WOMAN));

    Comparator<People> comparator = ComparatorUtils
        .transformedComparator(null, new PeopleNameTransformer());

    Set<People> actual = new TreeSet<People>(comparator);
    CollectionUtils.addAll(actual, input);

    List<People> expected = new ArrayList<People>();
    expected.add(this.buildPeople("name 1", "surname 1", null));
    expected.add(this.buildPeople("name 2", "surname 2", null));

    assertThat(actual).usingElementComparatorOnFields("name", "surname")
        .containsExactlyElementsOf(expected);
  }

}

A noter l'utilisation du framework AssertJ pour valider le résultat obtenu, qui est comparé par rapport à une liste intermédiaire, en ne comparant que la valeur de name et surname.