Поток Java: используйте необязательные операции filter () для объединения - PullRequest
0 голосов
/ 08 января 2019

Примечание: этот вопрос не относится к java.util.Optional.

При работе с потоками я часто использую логику, подобную этой:

 Stream<FooBar> stream = myInitialStream();
 if (needsFilter1) stream = stream.filter(c -> whatever1());
 if (needsFilter2) stream = stream.filter(c -> whatever2());
 ...
 return stream.collect(toList());

Я пытаюсь добиться преобразования приведенного выше кода в одно выражение с использованием цепочки. Я считаю это более читабельным и понятным. До сих пор я нашел единственный способ добиться этого:

return myInitialStream()
       .filter(needsFilter1? c->whatever1() : c->true)
       .filter(needsFilter2? c->whatever2() : c->true)
       .collect(toList());

Тем не менее, это приведет к ненужным вызовам этих тривиальных c->true lamdas, что может привести к некоторому снижению производительности при увеличении масштаба.

Итак, мой вопрос: есть ли лучший способ создания цепочечных потоковых выражений, которые включают в себя дополнительную фильтрацию?

ОБНОВЛЕНИЕ: Возможно, я не прояснил это достаточно, но суть моего вопроса - найти решение с одним выражением . Если бы мне пришлось использовать несколько операторов (например, для инициализации предиката), я мог бы также использовать первый кодовый блок моего вопроса, который по сути делает то же самое.

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Я немного опоздал с решением, в любом случае я оставлю его здесь.

У меня была идея написать конструктор для построения комплекса Predicate, но в итоге я получил класс FilterCondition и метод FilterCondition.combine.

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(FilterCondition.<String>combine(
                FilterCondition.of(() -> true, s -> s.contains("3")),
                FilterCondition.of(() -> true, s -> s.contains("2")),
                FilterCondition.of(() -> false, s -> s.isEmpty())
            ).toPredicate())
    .collect(Collectors.toList());

При статическом импорте FilterCondition.of и FilterCondition.combine, это выглядело бы еще лучше.

Stream.of("123", "1", "12345", "", "12", "", "2")
    .filter(combine(
                of(() -> true, s -> s.contains("3")),
                of(() -> true, s -> s.contains("2")),
                of(() -> false, String::isEmpty)
            ).toPredicate())
    .collect(Collectors.toList());

FilterCondition<T> - это, по сути, Predicate<T> с дополнительным условием проверки необходимости применения predicate.

FilterCondition.combine занимает около FilterCondition с и составляет комбинированное.

class FilterCondition<T> {
    private final Supplier<Boolean> filterEnabled;
    private final Predicate<T> predicate;

    private FilterCondition(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        this.filterEnabled = filterEnabled;
        this.predicate = predicate;
    }

    public static <T> FilterCondition<T> of(Supplier<Boolean> filterEnabled, Predicate<T> predicate) {
        return new FilterCondition<>(filterEnabled, predicate);
    }

    @SafeVarargs
    public static <T> FilterCondition<T> combine(FilterCondition<T>... conditions) {
        return new FilterCondition<>(
                () -> true,
                Arrays.stream(conditions).filter(i -> i.filterEnabled.get()).map(i -> i.predicate).reduce(Predicate::and).orElse(t -> true)
        );
    }

    public Predicate<T> toPredicate() {
        return predicate;
    }

}
0 голосов
/ 08 января 2019

Сцепите предикаты в соответствии с условиями, используя Predicate::and, возвращая новый Предикат.

Predicate<FooBar> predicate = c -> whatever();

if (condition1) { predicate = predicate.and(c -> whatever1()); }
if (condition2) { predicate = predicate.and(c -> whatever2()); }

List<FooBar> dest = list.stream()
    .filter(predicate)
    .collect(Collectors.toList());

При обновлении запрашивается одно выражение. В любом случае вам нужен источник сопоставленных условий для предикатов. Со структурой данных Map<Supplier<Boolean>, Predicate<Integer>>, где ключом является Supplier условия, решающего, следует ли использовать значение (Predicate<FooBar>).

Сократите записи карты до нового Predicate<FooBar>, используя цепочку этих Predicates с Predicate::and, для которых их Supplier<Boolean> возвращает true (условие действительно).

Имея Map из условий:

Map<Supplier<Boolean>, Predicate<FooBar>> map = new HashMap<>();
map.put(() -> needsFilter1, c -> whatever1());
map.put(() -> needsFilter2, c -> whatever2());
...

Вот одно выражение Stream:

List<Integer> dest = list
        .stream()
        .filter(map.entrySet()                            // filter with a predicate ...
                .stream()
                .filter(e -> e.getKey().get())            // .. where a condition is 'true'
                .map(Entry::getValue)                     // .. get Predicates
                .reduce(i -> true, (l, r) -> l.and(r)))   // .. reduce them using AND
        .collect(Collectors.toList());               
...