Guawa: Dlaczego nie ma funkcji Lists.filter ()?

86

Czy jest jakiś powód

Lists.transform()

ale nie

Lists.filter()

?

Jak poprawnie filtrować listę? mógłbym użyć

new ArrayList(Collection2.filter())

oczywiście, ale w ten sposób nie ma gwarancji, że moje zamówienie pozostanie niezmienione, jeśli dobrze rozumiem.

Fabian Zeindl
źródło
8
FYI, List.newArrayList (Iterables.filter (...)) jest generalnie szybszy niż nowy ArrayList (Collection2.filter (...)). Konstruktor ArrayList wywołuje funkcję size () w filtrowanej kolekcji, a obliczenie rozmiaru wymaga zastosowania filtru do każdego elementu oryginalnej listy.
Jared Levy,
4
@JaredLevy Może zamiast List.newArrayList(Iterables.filter(...)), powinno być Lists.newArrayList(Iterables.filter(...)) .
Abdull

Odpowiedzi:

57

Nie został zaimplementowany, ponieważ ujawniłby niebezpieczną dużą liczbę powolnych metod, takich jak #get (indeks) w zwróconym widoku listy (wywołując błędy wydajności). A ListIterator również byłby trudny do wdrożenia (chociaż przesłałem łatkę lata temu, aby to pokryć).

Ponieważ metody indeksowane nie mogą być wydajne w przefiltrowanym widoku listy, lepiej jest po prostu skorzystać z filtrowanej iterowalnej, która ich nie ma.

Dimitris Andreou
źródło
7
Zakładasz, że zostanie zwrócony widok listy. Jednak #filter może zostać zaimplementowany jako zwracający nową zmaterializowaną listę, czego właściwie oczekiwałbym od metody filtrującej dla list, w przeciwieństwie do metody Iterable.
Felix Leipold
@FelixLeipold Spowodowałoby to jednak zamulenie wód. Jak to jest, filterkonsekwentnie oznacza view (wraz z zachowaniem sugerujący) czy to Iterables.filter, Sets.filteritd Ponieważ Iterables.filterkombajnów łatwo copyOfna dowolny ImmutableCollection, uważam ten kompromis dobry projekt (vs wymyślanie dodatkowych metod i nazw, jak filteredCopyi etażerka , dla kombinacji prostych narzędzi).
Luke Usherwood
37

Możesz skorzystać Iterables.filter, co na pewno utrzyma porządek.

Zwróć uwagę, że konstruując nową listę, będziesz kopiować elementy (oczywiście tylko odniesienia) - więc nie będzie to podgląd na żywo na oryginalną listę. Tworzenie widoku byłoby dość trudne - rozważ taką sytuację:

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

To wymagałoby iteracji po całej oryginalnej liście, stosując filtr do wszystkiego. Przypuszczam, że mogłoby to wymagać, aby dopasowywanie predykatów nie zmieniało się przez cały czas istnienia widoku, ale nie byłoby to całkowicie satysfakcjonujące.

(To tylko zgadywanie, pamiętajcie. Może któryś z opiekunów guawy włączy się z prawdziwym powodem :)

Jon Skeet
źródło
1
Collections2.filter.iteratortylko dzwoni Iterables.filter, więc wynik jest taki sam.
skaffman
@skaffman: W takim przypadku użyłbym Iterables.filterwersji tylko dla przejrzystości.
Jon Skeet,
3
... chyba że potrzebujesz view.size()gdzieś później w kodzie :)
Xaerxess
28

Mógłbym new List(Collection2.filter())oczywiście skorzystać, ale w ten sposób nie ma gwarancji, że moje zamówienie pozostanie takie samo.

To nieprawda. Collections2.filter()jest funkcją ocenianą leniwie - w rzeczywistości nie filtruje kolekcji, dopóki nie zaczniesz uzyskiwać dostępu do przefiltrowanej wersji. Na przykład, jeśli iterujesz po przefiltrowanej wersji, przefiltrowane elementy wyskakują z iteratora w tej samej kolejności, co oryginalna kolekcja (oczywiście bez odfiltrowanych).

Być może myślałeś, że filtruje z góry, a następnie zrzuca wyniki do dowolnego, nieuporządkowanego zbioru jakiejś formy - tak nie jest.

Jeśli więc użyjesz danych wyjściowych Collections2.filter()jako danych wejściowych do nowej listy, oryginalna kolejność zostanie zachowana.

Używając importu statycznego (i Lists.newArrayListfunkcji), staje się dość zwięzły:

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

Zauważ, że while Collections2.filternie będzie chętnie iterował po kolekcji bazowej, Lists.newArrayList będzie - wyodrębni wszystkie elementy przefiltrowanej kolekcji i skopiuje je do nowej ArrayList.

skaffman
źródło
To bardziej jak: List filteredList = newArrayList(filter(originalList, new Predicate<T>() { @Override public boolean apply(T input) { return (...); } }));lub tj. List filteredList = newArrayList(filter(originalList, Predicates.notNull()));
Xaerxess
@Xaerxess: Ups, tak, zapomniałem predykatu ... naprawiono
skaffman
@Bozho: Dzięki ... zajęło mi to wystarczająco dużo czasu :)
skaffman
12

Jak wspomniał Jon, możesz użyć Iterables.filter(..)lub, Collections2.filter(..)a jeśli nie potrzebujesz podglądu na żywo, możesz użyć ImmutableList.copyOf(Iterables.filter(..))lub Lists.newArrayList( Iterables.filter(..))i tak, porządkowanie zostanie zachowane.

Jeśli naprawdę interesuje Cię dlaczego warto, możesz odwiedzić https://github.com/google/guava/issues/505, aby uzyskać więcej informacji.

Premraj
źródło
6

Podsumowując to, co powiedzieli inni, możesz łatwo utworzyć ogólne opakowanie do filtrowania list:

public static <T> List<T> filter(Iterable<T> userLists, Predicate<T> predicate) {
    return Lists.newArrayList(Iterables.filter(userLists, predicate));
}
Holger Brandl
źródło