Question Quel est le meilleur moyen de filtrer une collection Java?


Je veux filtrer un java.util.Collection basé sur un prédicat.


559
2017-09-23 16:26


origine


Réponses:


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

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

Voici un Didacticiel.

Utilisation Collection#removeIf modifier la collection en place. (Remarque: dans ce cas, le prédicat supprimera les objets qui satisfont le prédicat):

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

lambdaj permet de filtrer les 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?

Avertissement: Je suis un contributeur sur lambdaj


531
2017-09-06 13:37



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

Ajoutez d'abord cette interface à votre base de code.

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

Ses implémenteurs peuvent répondre quand un certain prédicat est vrai d'un certain type. Par exemple. Si T étaient User et AuthorizedUserPredicate<User> met en oeuvre IPredicate<T>, puis AuthorizedUserPredicate#apply retourne si le passé 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 la performance sur la vérification linéaire est préoccupante, alors je pourrais vouloir avoir un objet de domaine qui a la collection cible. L'objet de domaine qui a la collection cible aurait une logique de filtrage pour les méthodes qui initialisent, ajoutent et définissent la collection cible.

METTRE À JOUR:

Dans la classe utilitaire (disons Predicate), j'ai ajouté une méthode select 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 en tant que valeur par défaut lorsque l'instance est introuvable:

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

UPDATE (après la version Java 8):

Cela fait plusieurs années que je (Alan) posté pour la première fois cette réponse, et je ne peux toujours pas croire que je suis en train de recueillir des points SO pour cette réponse. Quoi qu'il en soit, maintenant que Java 8 a introduit des fermetures à la langue, ma réponse serait maintenant considérablement différente et plus simple. Avec Java 8, il n'y a pas besoin d'une classe d'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 JDK 8 pour les options a la capacité de get(), isPresent(), orElse(defaultUser), orElseGet(userSupplier) et orElseThrow(exceptionSupplier), ainsi que d'autres fonctions «monadiques» telles que map, flatMap et filter.

Si vous voulez simplement collecter tous les utilisateurs correspondant au prédicat, utilisez le 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 ici pour plus d'exemples sur le fonctionnement des flux Java 8.


213
2017-09-23 16:41



Utilisation CollectionUtils.filter (Collection, Prédicat), d'Apache Commons.


87
2017-09-23 16:28



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

Le plus simple (mais pas le 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'intégrer dans une méthode utilitaire. Puis inventez une interface IPredicate, créez une implémentation anonyme de cette interface et faites 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 doit être conservée dans la collection.

Je ne vois pas vraiment de justification pour l'introduction d'une bibliothèque tierce juste pour cette tâche.


60
2017-09-23 16:41



Considérer Google Collections pour un cadre de collections mis à jour qui prend en charge les génériques.

METTRE À JOUR: La bibliothèque de collections google est désormais obsolète. Vous devriez utiliser la dernière version de Goyave au lieu. Il a toujours les mêmes extensions à la structure des collections, y compris un mécanisme de filtrage basé sur un prédicat.


60
2017-09-23 16:29



Attendez Java 8:

List<Person> olderThan30 = 
  //Create a Stream from the personList
  personList.stream().
  //filter the element to select only those with age >= 30
  filter(p -> p.age >= 30).
  //put those filtered elements into a new List.
  collect(Collectors.toList());

23
2017-08-29 10:50



Depuis la version initiale de Java 8, vous pouvez essayer quelque chose comme:

Collection<T> collection = ...;
Stream<T> stream = collection.stream().filter(...);

Par exemple, si vous avez une liste d'entiers et que vous voulez filtrer les nombres supérieurs à 10, puis imprimer ces nombres sur la console, vous pouvez faire quelque chose comme:

List<Integer> numbers = Arrays.asList(12, 74, 5, 8, 16);
numbers.stream().filter(n -> n > 10).forEach(System.out::println);

10
2017-10-27 21:41



Je vais jeter RxJava dans l'anneau, qui est également disponible sur Android. RxJava n'est peut-être pas toujours la meilleure option, mais elle vous donnera plus de flexibilité si vous souhaitez ajouter plus de transformations sur votre collection ou gérer les erreurs pendant le filtrage.

Observable.from(Arrays.asList(1, 2, 3, 4, 5))
    .filter(new Func1<Integer, Boolean>() {
        public Boolean call(Integer i) {
            return i % 2 != 0;
        }
    })
    .subscribe(new Action1<Integer>() {
        public void call(Integer i) {
            System.out.println(i);
        }
    });

Sortie:

1
3
5

Plus de détails sur RxJava's filter peut être trouvé ici.


9
2017-07-24 02:13