Гуава: Чому немає функції Lists.filter ()?


86

Чи є якась причина

Lists.transform()

але не

Lists.filter()

?

Як правильно відфільтрувати список? Я міг би використати

new ArrayList(Collection2.filter())

звичайно, але таким чином не гарантується, що моє замовлення залишиться незмінним, якщо я правильно зрозумію.


8
FYI, List.newArrayList (Iterables.filter (...)), як правило, швидший за новий ArrayList (Collection2.filter (...)). Конструктор ArrayList викликає size () у відфільтрованій колекції, і обчислення розміру вимагає, щоб фільтр застосовувався до кожного елемента вихідного списку.
Джаред Леві,

4
@JaredLevy Можливо, замість List.newArrayList(Iterables.filter(...)), слід сказати Lists.newArrayList(Iterables.filter(...)) .
Абдулл

Відповіді:


57

Він не був реалізований, оскільки він виставив би небезпечну велику кількість повільних методів, таких як #get (index) у повернутому поданні списку (запрошення до помилок продуктивності). І ListIterator було б також важко реалізувати (хоча я надіслав виправлення років тому, щоб висвітлити це).

Оскільки індексовані методи не можуть бути ефективними у відфільтрованому поданні списку, краще просто скористатися відфільтрованим Iterable, який їх не має.


7
Ви припускаєте, що буде повернуто подання списку. Однак #filter може бути реалізований як повернення нового матеріалізованого списку, що насправді я би очікував від методу фільтрування списків, на відміну від Iterable.
Фелікс Лейпольд,

@FelixLeipold Однак це замутило б воду. Як би це не було, filterпослідовно означає погляд (разом із поведінкою, що передбачає), чи є це Iterables.filter, Sets.filterі т. Д. Оскільки Iterables.filterлегко поєднується з copyOfбудь-якими ImmutableCollection, я вважаю це хорошим компромісним дизайном (проти придумування додаткових методів та імен, наприклад filteredCopyчи ні , для комбінацій простих утиліт).
Luke Usherwood

37

Можна використовувати Iterables.filter , що однозначно збереже замовлення.

Зауважте, що, будуючи новий список, ви будете копіювати елементи (звичайно, лише посилання) - отже, це не буде прямим переглядом оригінального списку. Створення подання було б досить складно - розгляньте цю ситуацію:

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

Це повинно було б переглядати весь вихідний список, застосовуючи фільтр до всього. Я припускаю, що це може вимагати, щоб збіг предикатів не змінювався протягом усього періоду перегляду, але це було б не зовсім задовільним.

(Це лише здогадки, майте на увазі. Можливо, хтось із супровідників Гуави чіпне справжню причину :)


1
Collections2.filter.iteratorпросто дзвонить Iterables.filter, тож результат однаковий.
skaffman

@skaffman: У такому випадку я б використав Iterables.filterверсію лише для ясності.
Джон Скіт,

3
... якщо вам не потрібно view.size()десь пізніше в коді :)
Xaerxess

28

Я міг би скористатися, new List(Collection2.filter())звичайно, але таким чином не гарантовано, що моє замовлення залишиться незмінним.

Це неправда. Collections2.filter()є функцією лінивого оцінювання - вона фактично не фільтрує вашу колекцію, поки ви не почнете отримувати доступ до фільтрованої версії. Наприклад, якщо ви перебираєте фільтровану версію, тоді відфільтровані елементи вискакують з ітератора в тому самому порядку, що і ваша початкова колекція (за винятком тих, що відфільтровані, очевидно).

Можливо, ви думали, що він виконує фільтрування наперед, а потім скидає результати у довільну, невпорядковану колекцію певної форми - це не так.

Отже, якщо ви використовуєте вихідні дані Collections2.filter()як вхідні дані до нового списку, то ваше початкове замовлення буде збережено.

Використовуючи статичний імпорт (і Lists.newArrayListфункцію), стає досить стисло:

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

Зауважте, що, хоча Collections2.filterвін не буде з великим бажанням переглядати базову колекцію, Lists.newArrayList він - витягне всі елементи відфільтрованої колекції та скопіює їх у нову ArrayList.


Це більше схоже на: List filteredList = newArrayList(filter(originalList, new Predicate<T>() { @Override public boolean apply(T input) { return (...); } }));або тобто. List filteredList = newArrayList(filter(originalList, Predicates.notNull()));
Xaerxess

@Xaerxess: На жаль, так, забув присудок ... виправлено
skaffman

@Bozho: Дякую ... мені зайняло достатньо часу :)
skaffman

12

Як згадував Джон, ви можете використовувати Iterables.filter(..)або, Collections2.filter(..)і якщо вам не потрібен режим перегляду в реальному часі, ви можете використовувати ImmutableList.copyOf(Iterables.filter(..))або, Lists.newArrayList( Iterables.filter(..))і так, замовлення буде збережено.

Якщо вас справді цікавить, чому брати участь, ви можете відвідати https://github.com/google/guava/issues/505, щоб отримати докладнішу інформацію.


6

Підводячи підсумок того, що сказали інші, ви можете легко створити загальну обгортку для фільтрування списків:

public static <T> List<T> filter(Iterable<T> userLists, Predicate<T> predicate) {
    return Lists.newArrayList(Iterables.filter(userLists, predicate));
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.