web-dev-qa-db-ja.com

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

しかし、プログラミングスタイルを混在させずに、未知の数の述語をどのように適用できますか?知っていると、それはちょっといように見えます...

48
Paweł Dyda

私はあなたのFilterJava.util.function.Predicateとは異なるタイプであると仮定しています。つまり、それに適応する必要があります。動作する1つのアプローチは次のようになります。

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の背後にあるため、さらに1レベルの間接参照が導入されるため、全体的なパフォーマンスが向上するアプローチは明確ではありません。

適応の懸念がない場合(FilterPredicateである場合)、問題のステートメントははるかに単純になり、2番目のアプローチが明らかに勝利します。

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
41
Marko Topolnik

Collection<Predicate<T>> filtersがある場合は、reductionと呼ばれるプロセスを使用して、その中からいつでも単一の述語を作成できます。

Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);

または

Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);

フィルターの組み合わせ方によって異なります。

orElse呼び出しで指定された空の述語コレクションのフォールバックがアイデンティティロール(述語のandingに対してx->trueが行い、oringに対してx->falseが行う)を満たす場合、reduce(x->true, Predicate::and)またはreduce(x->false, Predicate::or)を使用して取得することもできますただし、非常に小さなコレクションの場合、フィルターが1つの述部のみを含む場合でもID述部とコレクションの述部を常に結合するため、フィルターの効率はわずかに低下します。対照的に、上記のバリアントreduce(accumulator).orElse(fallback)は、コレクションのサイズが1である場合、単一の述語を返します。


このパターンが同様の問題にどのように適用されるかに注意してください:Collection<Consumer<T>>を使用すると、単一のConsumer<T>を使用して作成できます

Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});

等。

48
Holger

ユーザーが1つのフィルター操作で述語のリストを適用したい場合、このような問題を解決することができました。

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」論理演算子と結合されることに注意してください。 「OR」と組み合わせるには、reduce行を次のようにする必要があります。

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
8
Uziel Sulkies

これは、この問題を解決する興味深い方法です( 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());

編集:predicatesToIgnoreが述語のリストであるループを処理する方法は次のとおりです。そこから単一の述語predicateToIgnoreを作成します。

Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
    predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}

次に、この単一の述部でフィルターを実行します。これにより、より良いフィルターが作成されます

7
Gunith D