Goiaba: Por que não há função Lists.filter ()?

86

Existe uma razão para isso

Lists.transform()

mas não

Lists.filter()

?

Como posso filtrar uma lista corretamente? eu poderia usar

new ArrayList(Collection2.filter())

claro, mas assim não é garantido que a minha encomenda continue a mesma, se bem entendi.

Fabian Zeindl
fonte
8
FYI, List.newArrayList (Iterables.filter (...)) é geralmente mais rápido do que novo ArrayList (Collection2.filter (...)). O construtor ArrayList chama size () na coleção filtrada e o cálculo do tamanho requer que o filtro seja aplicado a cada elemento da lista original.
Jared Levy
4
@JaredLevy Talvez em vez de List.newArrayList(Iterables.filter(...)), deveria dizer Lists.newArrayList(Iterables.filter(...)) .
Abdull

Respostas:

57

Não foi implementado porque exporia um grande número perigoso de métodos lentos, como #get (index) na exibição de Lista retornada (convidando bugs de desempenho). E ListIterator também seria difícil de implementar (embora eu tenha enviado um patch anos atrás para cobrir isso).

Como os métodos indexados não podem ser eficientes na visualização de Lista filtrada, é melhor apenas ir com um Iterable filtrado, que não os tem.

Dimitris Andreou
fonte
7
Você está assumindo que uma exibição de lista seria retornada. No entanto, #filter pode ser implementado para retornar uma nova lista materializada, que é realmente o que eu esperaria de um método de filtro para listas em oposição ao de Iterable.
Felix Leipold
@FelixLeipold Isso tornaria as águas turvas. Como está, filtersignifica consistentemente uma visão (junto com o comportamento que implica) seja isso Iterables.filter, Sets.filteretc. Uma vez que Iterables.filtercombina facilmente com copyOfem qualquer ImmutableCollection, considero esta uma boa compensação de design (versus surgindo com métodos e nomes extras, como filteredCopyou outros enfeites , para combinações de utilitários simples).
Luke Usherwood
37

Você pode usar Iterables.filter, o que definitivamente manterá os pedidos.

Observe que, ao construir uma nova lista, você estará copiando os elementos (apenas referências, é claro) - portanto, não será uma exibição ao vivo na lista original. Criar uma visualização seria bem complicado - considere esta situação:

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);

Isso teria que iterar por toda a lista original, aplicando o filtro a tudo. Suponho que isso possa exigir que a correspondência de predicado não mude ao longo do tempo de vida da visualização, mas isso não seria inteiramente satisfatório.

(Isso é apenas adivinhação, veja bem. Talvez um dos mantenedores do Guava venha a contribuir com o verdadeiro motivo :)

Jon Skeet
fonte
1
Collections2.filter.iteratorapenas chama Iterables.filter, então o resultado é o mesmo.
Skaffman
@skaffman: Nesse caso, eu usaria a Iterables.filterversão apenas para maior clareza.
Jon Skeet
3
... a menos que você precise de um view.size()lugar posterior no código :)
Xaerxess
28

new List(Collection2.filter())Claro que eu poderia usar , mas dessa forma não é garantido que meu pedido permaneça o mesmo.

Isso não é verdade. Collections2.filter()é uma função avaliada vagarosamente - ela não filtra realmente sua coleção até que você comece a acessar a versão filtrada. Por exemplo, se você iterar sobre a versão filtrada, os elementos filtrados sairão do iterador na mesma ordem que sua coleção original (menos os filtrados, obviamente).

Talvez você estivesse pensando que ele faz a filtragem antecipadamente e, em seguida, despeja os resultados em uma coleção arbitrária e não ordenada de alguma forma - não faz.

Portanto, se você usar a saída de Collections2.filter()como entrada para uma nova lista, seu pedido original será mantido.

Usando importações estáticas (e a Lists.newArrayListfunção), torna-se bastante sucinto:

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

Observe que, embora Collections2.filternão vá iterar avidamente sobre a coleção subjacente, Lists.newArrayList irá - ele irá extrair todos os elementos da coleção filtrada e copiá-los em uma nova ArrayList.

skaffman
fonte
É mais como: 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: Opa, sim, esqueci o predicado ... corrigido
skaffman
@Bozho: Obrigado ... demorei bastante :)
skaffman
12

Conforme mencionado por Jon, você pode usar Iterables.filter(..)ou Collections2.filter(..)e se não precisar de uma visualização ao vivo, você pode usar ImmutableList.copyOf(Iterables.filter(..))ou Lists.newArrayList( Iterables.filter(..))e sim, o pedido será mantido.

Se você estiver realmente interessado em saber por que parte, você pode visitar https://github.com/google/guava/issues/505 para obter mais detalhes.

Premraj
fonte
6

Resumindo o que os outros disseram, você pode criar facilmente um wrapper genérico para filtrar listas:

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