Guava: Pourquoi n'y a-t-il pas de fonction Lists.filter ()?

86

Y a-t-il une raison pour laquelle il y a

Lists.transform()

mais non

Lists.filter()

?

Comment filtrer correctement une liste? je pourrais utiliser

new ArrayList(Collection2.filter())

bien sûr, mais de cette façon il n'est pas garanti que ma commande reste la même, si je comprends bien.

Fabian Zeindl
la source
8
Pour info, List.newArrayList (Iterables.filter (...)) est généralement plus rapide que la nouvelle ArrayList (Collection2.filter (...)). Le constructeur ArrayList appelle size () sur la collection filtrée et le calcul de la taille nécessite que le filtre soit appliqué à chaque élément de la liste d'origine.
Jared Levy
4
@JaredLevy Peut-être au lieu de List.newArrayList(Iterables.filter(...)), il devrait dire Lists.newArrayList(Iterables.filter(...)) .
Abdull

Réponses:

57

Il n'a pas été implémenté car il exposerait un grand nombre périlleux de méthodes lentes, telles que #get (index) sur la vue de liste renvoyée (invitant des bogues de performances). Et ListIterator serait également difficile à implémenter (même si j'ai soumis un correctif il y a des années pour couvrir cela).

Étant donné que les méthodes indexées ne peuvent pas être efficaces dans la vue Liste filtrée, il est préférable d'utiliser simplement un Iterable filtré, qui ne les a pas.

Dimitris Andreou
la source
7
Vous supposez qu'une vue de liste serait renvoyée. Cependant #filter pourrait être implémenté en renvoyant une nouvelle liste matérialisée, ce que j'attendais d'une méthode de filtrage pour les listes par opposition à celle sur Iterable.
Felix Leipold
@FelixLeipold Cela brouillerait les eaux cependant. Dans l'état actuel des choses, filtersignifie systématiquement une vue (avec le comportement que cela implique) que ce soit Iterables.filter, Sets.filteretc. Comme il se Iterables.filtercombine facilement avec copyOfn'importe lequel ImmutableCollection, je trouve que c'est un bon compromis de conception (vs proposer des méthodes et des noms supplémentaires, comme filteredCopyou autre) , pour des combinaisons d'utilitaires simples).
Luke Usherwood du
37

Vous pouvez utiliser Iterables.filter, ce qui maintiendra définitivement la commande.

Notez qu'en construisant une nouvelle liste, vous allez copier les éléments (juste des références, bien sûr) - donc ce ne sera pas une vue en direct sur la liste d'origine. Créer une vue serait assez délicat - considérez cette situation:

Predicate<StringBuilder> predicate = 
    /* predicate returning whether the builder is empty */
List<StringBuilder> builders = Lists.newArrayList();
List<StringBuilder> view = Lists.filter(builders, predicate);

for (int i = 0; i < 10000; i++) {
    builders.add(new StringBuilder());
}
builders.get(8000).append("bar");

StringBuilder firstNonEmpty = view.get(0);

Cela devrait parcourir toute la liste d'origine, en appliquant le filtre à tout. Je suppose que cela pourrait exiger que la correspondance de prédicat ne change pas pendant la durée de vie de la vue, mais ce ne serait pas entièrement satisfaisant.

(Ceci est juste une supposition, remarquez. Peut-être que l'un des responsables de Guava expliquera la vraie raison :)

Jon Skeet
la source
1
Collections2.filter.iteratorappelle juste Iterables.filter, donc le résultat est le même.
skaffman
@skaffman: Dans ce cas, j'utiliserais la Iterables.filterversion juste pour plus de clarté.
Jon Skeet
3
... sauf si vous avez besoin d'un view.size()quelque part plus tard dans le code :)
Xaerxess
28

Je pourrais new List(Collection2.filter())bien sûr utiliser , mais de cette façon, il n'est pas garanti que ma commande reste la même.

Ce n'est pas vrai. Collections2.filter()est une fonction évaluée paresseusement - elle ne filtre pas réellement votre collection tant que vous ne commencez pas à accéder à la version filtrée. Par exemple, si vous parcourez la version filtrée, les éléments filtrés sortiront de l'itérateur dans le même ordre que votre collection d'origine (moins ceux filtrés, évidemment).

Peut-être pensiez-vous qu'il effectue le filtrage à l'avance, puis vide les résultats dans une collection arbitraire et non ordonnée d'une certaine forme - ce n'est pas le cas.

Ainsi, si vous utilisez la sortie de Collections2.filter()comme entrée d'une nouvelle liste, votre commande d'origine sera conservée.

En utilisant les importations statiques (et la Lists.newArrayListfonction), cela devient assez succinct:

List filteredList = newArrayList(filter(originalList, predicate));

Notez que tout Collections2.filterne sera pas avec impatience parcourir la collection sous - jacente, Lists.newArrayList sera - il extraira tous les éléments de la collection filtrée et les copier dans un nouveau ArrayList.

skaffman
la source
C'est plus comme: List filteredList = newArrayList(filter(originalList, new Predicate<T>() { @Override public boolean apply(T input) { return (...); } }));ou ie. List filteredList = newArrayList(filter(originalList, Predicates.notNull()));
Xaerxess
@Xaerxess: Oups, oui, j'ai oublié le prédicat ... corrigé
skaffman
@Bozho: Merci ... m'a pris assez de temps :)
skaffman
12

Comme mentionné par Jon, vous pouvez utiliser Iterables.filter(..)ou Collections2.filter(..)et si vous n'avez pas besoin d'une vue en direct, vous pouvez utiliser ImmutableList.copyOf(Iterables.filter(..))ou Lists.newArrayList( Iterables.filter(..))et oui, la commande sera maintenue.

Si vous êtes vraiment intéressé par le pourquoi de la partie, vous pouvez visiter https://github.com/google/guava/issues/505 pour plus de détails.

Premraj
la source
6

Pour résumer ce que les autres ont dit, vous pouvez facilement créer un wrapper générique pour filtrer les listes:

public static <T> List<T> filter(Iterable<T> userLists, Predicate<T> predicate) {
    return Lists.newArrayList(Iterables.filter(userLists, predicate));
}
Holger Brandl
la source