711 votes

Quelle est la meilleure façon de filtrer une collection Java ?

Je veux filtrer un java.util.Collection sur la base d'un prédicat.

753voto

Mario Fusco Points 4163

Java 8 ( 2014 ) résout ce problème en utilisant les flux et les lambdas en une seule ligne de code :

List<Person> beerDrinkers = persons.stream()
    .filter(p -> p.getAge() > 16).collect(Collectors.toList());

Voici un tutoriel .

Utilice Collection#removeIf pour modifier la collection en place. (Remarque : Dans ce cas, le prédicat va supprimer les objets qui satisfont le prédicat) :

persons.removeIf(p -> p.getAge() <= 16);

lambdaj permet de filtrer des collections sans écrire de boucles ou de classes internes :

List<Person> beerDrinkers = select(persons, having(on(Person.class).getAge(),
    greaterThan(16)));

Pouvez-vous imaginer quelque chose de plus lisible ?

Avis de non-responsabilité : Je suis un contributeur sur lambdaj

36 votes

C'est bien mais les importations statiques obscurcissent ce qui se passe. Pour référence, select/having/on sont des importations statiques de ch.lambdaj.Lambda, greaterThan est org.hamcrest.Matchers.

11 votes

LambdaJ est vraiment sexy, mais il convient de noter qu'il implique une surcharge significative (2,6 en moyenne) : code.google.com/p/lambdaj/wiki/PerformanceAnalysis .

7 votes

Apparemment, ça ne fonctionne pas sur Android : groups.google.com/forum/#!msg/lambdaj/km7uFgvSd3k/grJhgl3ik5sJ

228voto

Alan Points 4249

En supposant que vous utilisez Java 1.5 et que vous ne pouvez pas ajouter Collections Google je ferais quelque chose de très similaire à ce que les gars de Google ont fait. C'est une légère variation sur les commentaires de Jon.

Ajoutez d'abord cette interface à votre codebase.

public interface IPredicate<T> { boolean apply(T type); }

Ses implémenteurs peuvent répondre quand un certain prédicat est vrai pour un certain type. Par exemple, si T étaient User y AuthorizedUserPredicate<User> met en œuvre IPredicate<T> entonces AuthorizedUserPredicate#apply renvoie si l'objet passé en User est autorisé.

Ensuite, dans une classe d'utilité, vous pourriez dire

public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
    Collection<T> result = new ArrayList<T>();
    for (T element: target) {
        if (predicate.apply(element)) {
            result.add(element);
        }
    }
    return result;
}

Donc, en supposant que vous avez l'utilisation de ce qui précède pourrait être

Predicate<User> isAuthorized = new Predicate<User>() {
    public boolean apply(User user) {
        // binds a boolean method in User to a reference
        return user.isAuthorized();
    }
};
// allUsers is a Collection<User>
Collection<User> authorizedUsers = filter(allUsers, isAuthorized);

Si les performances de la vérification linéaire sont importantes, il est préférable d'avoir un objet de domaine qui possède la collection cible. L'objet de domaine qui possède la collection cible aurait une logique de filtrage pour les méthodes qui initialisent, ajoutent et définissent la collection cible.

UPDATE :

Dans la classe utilitaire (disons Predicate), j'ai ajouté une méthode de sélection avec une option pour la valeur par défaut lorsque le prédicat ne renvoie pas la valeur attendue, et aussi une propriété statique pour les paramètres à utiliser dans le nouvel IPredicate.

public class Predicate {
    public static Object predicateParams;

    public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
        Collection<T> result = new ArrayList<T>();
        for (T element : target) {
            if (predicate.apply(element)) {
                result.add(element);
            }
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate) {
        T result = null;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate, T defaultValue) {
        T result = defaultValue;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }
}

L'exemple suivant recherche les objets manquants entre les collections :

List<MyTypeA> missingObjects = (List<MyTypeA>) Predicate.filter(myCollectionOfA,
    new IPredicate<MyTypeA>() {
        public boolean apply(MyTypeA objectOfA) {
            Predicate.predicateParams = objectOfA.getName();
            return Predicate.select(myCollectionB, new IPredicate<MyTypeB>() {
                public boolean apply(MyTypeB objectOfB) {
                    return objectOfB.getName().equals(Predicate.predicateParams.toString());
                }
            }) == null;
        }
    });

L'exemple suivant, recherche une instance dans une collection, et renvoie le premier élément de la collection comme valeur par défaut si l'instance n'est pas trouvée :

MyType myObject = Predicate.select(collectionOfMyType, new IPredicate<MyType>() {
public boolean apply(MyType objectOfMyType) {
    return objectOfMyType.isDefault();
}}, collectionOfMyType.get(0));

MISE À JOUR (après la sortie de Java 8) :

Cela fait plusieurs années que j'ai (Alan) posté cette réponse pour la première fois, et je n'arrive toujours pas à croire que j'accumule des points SO pour cette réponse. Quoi qu'il en soit, maintenant que Java 8 a introduit les closures dans le langage, ma réponse serait considérablement différente et plus simple. Avec Java 8, il n'y a pas besoin d'une classe utilitaire statique distincte. Donc si vous voulez trouver le 1er élément qui correspond à votre prédicat.

final UserService userService = ... // perhaps injected IoC
final Optional<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).findFirst();

L'API du JDK 8 pour les optionnels permet de get() , isPresent() , orElse(defaultUser) , orElseGet(userSupplier) y orElseThrow(exceptionSupplier) ainsi que d'autres fonctions "monadiques" telles que map , flatMap y filter .

Si vous voulez simplement collecter tous les utilisateurs qui correspondent au prédicat, utilisez l'option Collectors pour terminer le flux dans la collection souhaitée.

final UserService userService = ... // perhaps injected IoC
final List<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).collect(Collectors.toList());

Voir aquí pour plus d'exemples sur le fonctionnement des flux Java 8.

27 votes

Oui, mais je déteste réinventer la roue, encore et encore. Je préfère trouver une bibliothèque utilitaire qui fait ce que je veux.

2 votes

Ce n'est pas la meilleure solution si vous ne voulez pas de la nouvelle collection. Utilisez la métaphore de l'itérateur de filtre, qui peut donner lieu à une nouvelle collection, ou qui peut être tout ce dont vous avez besoin.

0 votes

@Nestor : dans une compréhension Scala, le filtrage serait beaucoup plus simple : val authorized = for (user <- users if user.isAuthorized) yield user

92voto

Kevin Wong Points 3730

3 votes

C'est bien, mais ce n'est pas un générique, et il modifie la collection en place (pas sympa)

2 votes

Il existe d'autres méthodes de filtrage dans CollectionUtils qui ne modifient pas la collection originale.

43 votes

En particulier, la méthode qui fait no modifier la collection en place est org.apache.commons.collections.CollectionUtils#select(Collection,Predicate)

67voto

Vladimir Dyuzhev Points 10647

Le "meilleur" moyen est une demande trop large. Est-ce "le plus court" ? "La plus rapide ? "Lisible" ? Filtrer sur place ou dans une autre collection ?

La façon la plus simple (mais pas la plus lisible) est de l'itérer et d'utiliser la méthode Iterator.remove() :

Iterator<Foo> it = col.iterator();
while( it.hasNext() ) {
  Foo foo = it.next();
  if( !condition(foo) ) it.remove();
}

Maintenant, pour le rendre plus lisible, vous pouvez l'envelopper dans une méthode utilitaire. Puis inventer une interface IPredicate, créer une implémentation anonyme de cette interface et faire quelque chose comme :

CollectionUtils.filterInPlace(col,
  new IPredicate<Foo>(){
    public boolean keepIt(Foo foo) {
      return foo.isBar();
    }
  });

où filterInPlace() itère la collection et appelle Predicate.keepIt() pour savoir si l'instance à conserver dans la collection.

Je ne vois pas vraiment de raison de faire appel à une bibliothèque tierce uniquement pour cette tâche.

6 votes

Mon vote va à celui-ci : il fonctionne tout simplement, sans bibliothèques externes. Je n'ai jamais réalisé que l'instanciation d'un Iterator pouvait être utile par rapport à l'utilisation de la syntaxe for-each, ou que l'on pouvait supprimer des éléments d'une liste sans une ConcurrentModificationException ou quelque chose comme ça :)

1 votes

Je pense que c'est la meilleure façon d'utiliser la librairie Java standard sans la copier. Pour la 1.8, il y aurait le stream() mais tout le monde ne peut pas jouer avec les nouveaux jouets :P

0 votes

Est-ce que cela modifie aussi la collection originale ? @ZeroOne

62voto

Heath Borders Points 8067

Pensez à Collections Google pour une mise à jour de la structure Collections qui prend en charge les génériques.

UPDATE : La bibliothèque google collections est désormais dépréciée. Vous devez utiliser la dernière version de Goyave à la place. Il dispose toujours des mêmes extensions au cadre des collections, y compris un mécanisme de filtrage basé sur un prédicat.

0 votes

Oui, je connaissais la bibliothèque des collections Google. La version que j'utilisais ne contenait pas Collections2. J'ai ajouté une nouvelle réponse à cette question qui liste la méthode spécifique.

7 votes

Kevin, Iterables.filter() et Iterators.filter() sont là depuis le début, et sont généralement tout ce dont vous avez besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X