Как применить несколько предикатов к java.util.Stream?
Как я могу применить несколько предикатов к методу java.util.Stream's
filter()
?
Это то, что я делаю сейчас, но мне это не очень нравится. У меня есть Collection
вещей, и мне нужно уменьшить количество вещей на основе Collection
фильтров (предикатов):
Collection<Thing> things = someGenerator.someMethod();
List<Thing> filtered = things.parallelStream().filter(p -> {
for (Filter f : filtersCollection) {
if (f.test(p))
return true;
}
return false;
}).collect(Collectors.toList());
Я знаю, что если бы я знал количество фильтров вверх, я мог бы сделать что-то вроде этого:
List<Thing> filtered = things.parallelStream().filter(filter1).or(filter2).or(filter3)).collect(Collectors.toList());
Но как я могу применить неизвестное число предикатов без смешивания стилей программирования? Знайте, что это выглядит как-то уродливо...
Ответы
Ответ 1
Я предполагаю, что ваш Filter
- это тип, отличный от java.util.function.Predicate
, что означает, что он должен быть адаптирован к нему. Один из подходов, который будет работать, выглядит следующим образом:
things.stream().filter(t -> filtersCollection.stream().anyMatch(f -> f.test(t)));
Это приводит к небольшому результативному результату воссоздания потока фильтра для каждой оценки предикатов. Чтобы избежать этого, вы можете обернуть каждый фильтр в Predicate
и составить их:
things.stream().filter(filtersCollection.stream().<Predicate>map(f -> f::test)
.reduce(Predicate::or).orElse(t->false));
Однако, поскольку теперь каждый фильтр находится за его собственным Predicate
, вводя еще один уровень косвенности, неясно, какой подход будет иметь лучшую общую производительность.
Без проблем адаптации (если ваш Filter
оказывается Predicate
), утверждение проблемы становится намного проще, и второй подход явно выигрывает:
things.stream().filter(
filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
Ответ 2
Если у вас есть Collection<Predicate<T>> filters
, вы всегда можете создать из него один предикат, используя процесс, называемый сокращением:
Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);
или
Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);
в зависимости от того, как вы хотите объединить фильтры.
Если резервная копия для пустого набора предикатов, указанного в вызове orElse
, выполняет роль идентичности (для x->true
для and
для предикатов и x->false
для or
ing) вы также можете использовать reduce(x->true, Predicate::and)
или reduce(x->false, Predicate::or)
, чтобы получить фильтр, но это немного менее эффективно для очень маленьких коллекций, поскольку он всегда будет комбинировать предикат идентификации с предикатом коллекций, даже если он содержит только один предикат. Напротив, вариант reduce(accumulator).orElse(fallback)
, показанный выше, вернет единственный предикат, если коллекция имеет размер 1
.
Обратите внимание, что этот шаблон применим и к аналогичным проблемам: имея Collection<Consumer<T>>
, вы можете создать один Consumer<T>
, используя
Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});
Etc.
Ответ 3
Это интересный способ решения этой проблемы (прямое вставка http://www.leveluplunch.com/java/tutorials/006-how-to-filter-arraylist-stream-java8/). Я думаю, что это более эффективный способ.
Predicate<BBTeam> nonNullPredicate = Objects::nonNull;
Predicate<BBTeam> nameNotNull = p -> p.teamName != null;
Predicate<BBTeam> teamWIPredicate = p -> p.teamName.equals("Wisconsin");
Predicate<BBTeam> fullPredicate = nonNullPredicate.and(nameNotNull)
.and(teamWIPredicate);
List<BBTeam> teams2 = teams.stream().filter(fullPredicate)
.collect(Collectors.toList());
EDIT: здесь, как иметь дело с циклами, где predicatesToIgnore - это список предикатов. Я создаю из него предикат predicateToIgnore.
Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}
Затем сделайте фильтр с этим единственным предикатом. Это создает лучший фильтр IMHO
Ответ 4
Мне удалось решить такую проблему, если пользователь хочет применить список предикатов в одной операции фильтра, список, который может быть динамическим и не указан, который должен быть сведен к одному предикату - например:
public class TestPredicates {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(numbers.stream()
.filter(combineFilters(x -> x > 2, x -> x < 9, x -> x % 2 == 1))
.collect(Collectors.toList()));
}
public static <T> Predicate<T> combineFilters(Predicate<T>... predicates) {
Predicate<T> p = Stream.of(predicates).reduce(x -> true, Predicate::and);
return p;
}
}
Обратите внимание, что это объединит их с логическим оператором "AND".
Для объединения с "ИЛИ" линия сокращения должна быть:
Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);