【问题标题】:Java stream: use optional filter() operations on chainingJava 流:对链接使用可选的 filter() 操作
【发布时间】:2019-01-08 14:18:29
【问题描述】:

注意:这个问题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-&gt;true lamdas 进行不必要的调用,这可能会在扩展时产生一些性能成本。

所以我的问题是:有没有更好的方法来生成包含可选过滤的链式流表达式?

更新:也许我说得不够清楚,但我的问题的重点是找到一个单表达式解决方案。如果我必须使用多个语句(例如,初始化谓词),我还可以使用问题的第一个代码块,它本质上是相同的。

【问题讨论】:

  • needsFilter1needsFilter2 总是保证为真?
  • 不,这些条件彼此无关,可以是任何情况。此外,我需要执行的过滤操作并不总是两个。我只列出了两个以使帖子更短,但可能更多。我正在寻找一种无论您要执行多少过滤都可以工作的模式。
  • 您可以使用.filter(c-&gt;(needsFilter1 &amp;&amp; whatever1()),它比您的三元表达式更短且更具可读性,并希望 JIT 能够意识到 needsFilter1 是一个常量(或不变)表达式并对其进行优化。跨度>
  • @KlitosKyriacou 如果你使用.filter(c-&gt;(needsFilter1 &amp;&amp; whatever1()) 并且needsFilter1false,你最终会得到一个为流的所有元素返回false 的谓词。显然,这是错误的,因为 OP 在这种情况下想要跳过过滤。
  • @FedericoPeraltaSchaffner 好收获!我的意思是.filter(c-&gt;(!needsFilter1 || whatever1())

标签: java lambda java-8 java-stream predicate


【解决方案1】:

使用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&lt;Supplier&lt;Boolean&gt;, Predicate&lt;Integer&gt;&gt;,其中键是决定是否应使用值(Predicate&lt;FooBar&gt;)的条件的Supplier

使用将这些PredicatesPredicate::and 链接,将映射的条目减少到新的Predicate&lt;FooBar&gt;,它们的Supplier&lt;Boolean&gt; 返回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());               

【讨论】:

  • 如前所述,我经常将这种逻辑与不同的过滤器组合一起使用。每次都必须编写带有返回谓词的标志的辅助方法对我没有帮助。如果没有办法获得单一表达式的解决方案,我会选择我的问题的第一个代码块,这很好,但并不理想。无论如何,我的问题是是否有办法实现这一目标。这是否是一个好主意是一个相当主观的问题。
  • @Nikolas 非常感谢!这现在是天才的想法。因此,当我了解您的方法时,我可以使用地图或集合来构建我的流。这些将提供要使用的条件和过滤器。在最坏的情况下,我会有一个 o->true 过滤的开销,但这只是一个。 filter() 部分也可以放在一个 utils 函数中,该函数只编写一次并在任何地方使用。对我来说看起来很棒!
  • 我认为你应该将filter 的东西移到一个方法中,因为它现在看起来很麻烦且无法维护。不管怎样,我喜欢这个主意,和我的很相似。
  • @AndrewTobilko:是的,安德鲁,为了简洁起见和 OP 的要求,我包含了这样一种单语句方法。即使是最小的程序,我也更喜欢使用对象。
  • 您可以使用BooleanSupplier 而不是Supplier&lt;Boolean&gt;。此外,您可以使用.reduce(Predicate::and).orElse(i -&gt; true) 代替.reduce(i -&gt; true, (l, r) -&gt; l.and(r)),这样可以避免将现有谓词与i-&gt;true 组合起来(事实上,如果只剩下一个谓词,您将获得原始谓词)。
【解决方案2】:

我的解决方案有点晚了,无论如何我会把它留在这里。

我有一个想法是编写一个构建器来构造一个复杂的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.ofFilterCondition.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&lt;T&gt; 基本上是一个Predicate&lt;T&gt;,带有一个额外的条件来检查是否应该应用predicate

FilterCondition.combine 需要一些 FilterConditions 并组成一个组合。

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

}

【讨论】:

  • FilterCondition 执行Predicate&lt;T&gt; 吗?缺点:get 方法将针对接口联系人实现。灵活使用各种构造函数。对象将成为谓词,而不仅仅是提供它。你怎么看?有我的 +1 好方法:))
  • @Nikolas 你暗示了什么样的Predicate 的构造函数?
  • @AndrewTobilko Noone。 Predicate 是一个功能接口。我的意思是你的自定义类可以灵活地使用更多的构造函数。
  • @Nikolas 什么构造函数?你能举个例子吗?
  • 我认为@Nikolas 的意思是您可以使用 lambda 语法生成 Predicate 实例。它在技术上不是构造函数,但它的作用类似于构造函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-28
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
  • 2021-12-23
相关资源
最近更新 更多