如何在Java 8中动态进行过滤?

2024-01-03

我知道在 Java 8 中,我可以像这样进行过滤:

List<User> olderUsers = users.stream().filter(u -> u.age > 30).collect(Collectors.toList());

但是,如果我有一个集合和六个过滤条件,并且我想测试这些条件的组合,该怎么办?

例如,我有一个对象集合和以下条件:

<1> Size
<2> Weight
<3> Length
<4> Top 50% by a certain order
<5> Top 20% by a another certain ratio
<6> True or false by yet another criteria

我想测试上述标准的组合,例如:

<1> -> <2> -> <3> -> <4> -> <5>
<1> -> <2> -> <3> -> <5> -> <4>
<1> -> <2> -> <5> -> <4> -> <3>
...
<1> -> <5> -> <3> -> <4> -> <2>
<3> -> <2> -> <1> -> <4> -> <5>
...
<5> -> <4> -> <3> -> <3> -> <1>

如果每个测试顺序可能会给我不同的结果,如何编写一个循环来自动过滤所有组合?

我能想到的是使用另一种方法来生成测试顺序,如下所示:

int[][] getTestOrder(int criteriaCount)
{
 ...
}

So if the criteriaCount is 2, it will return : {{1,2},{2,1}}
If the criteriaCount is 3, it will return : {{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,1,2},{3,2,1}}
...

那么如何利用Java 8自带的简洁表达式的过滤机制来最高效地实现呢?


有趣的问题。这里发生了几件事。毫无疑问,这个问题可以用不到半页的 Haskell 或 Lisp 来解决,但这是 Java,所以我们开始......

一个问题是我们有数量可变的过滤器,而大多数已显示的示例都说明了固定的管道。

另一个问题是,OP 的某些“过滤器”是上下文相关的,例如“按特定顺序排列的前 50%”。这不能通过简单的操作来完成filter(predicate)在流上构建。

关键是要认识到,虽然 lambda 允许函数作为参数传递(效果良好),但这也意味着它们可以存储在数据结构中并且可以对其执行计算。最常见的计算是采用多个函数并将它们组合起来。

假设正在操作的值是 Widget 的实例,它是一个具有一些明显 getter 的 POJO:

class Widget {
    String name() { ... }
    int length() { ... }
    double weight() { ... }

    // constructors, fields, toString(), etc.
}

让我们从第一个问题开始,弄清楚如何使用可变数量的简单谓词进行操作。我们可以创建一个谓词列表,如下所示:

List<Predicate<Widget>> allPredicates = Arrays.asList(
    w -> w.length() >= 10,
    w -> w.weight() > 40.0,
    w -> w.name().compareTo("c") > 0);

给定这个列表,我们可以对它们进行排列(可能没有用,因为它们与顺序无关)或选择我们想要的任何子集。假设我们只想应用所有这些。我们如何将可变数量的谓词应用于流?有一个Predicate.and()方法将采用两个谓词并使用逻辑组合它们and,返回单个谓词。因此,我们可以采用第一个谓词并编写一个循环,将其与后续谓词组合起来,以构建一个复合谓词and他们所有人中:

Predicate<Widget> compositePredicate = allPredicates.get(0);
for (int i = 1; i < allPredicates.size(); i++) {
    compositePredicate = compositePredicate.and(allPredicates.get(i));
}

这是可行的,但如果列表为空,它就会失败,并且由于我们现在正在进行函数式编程,因此在循环中改变变量是 declassé。但是瞧!这就是减少!我们可以减少所有谓词and运算符获取单个复合谓词,如下所示:

Predicate<Widget> compositePredicate =
    allPredicates.stream()
                 .reduce(w -> true, Predicate::and);

(来源:我从@venkat_s https://twitter.com/venkat_s。如果有机会,请去看他在会议上的演讲。他很好。)

注意使用w -> true作为减少的身份值。 (这也可以用作初始值compositePredicatefor 循环,这将修复零长度列表的情况。)

现在我们有了复合谓词,我们可以编写一个简短的管道,简单地将复合谓词应用于小部件:

widgetList.stream()
          .filter(compositePredicate)
          .forEach(System.out::println);

上下文相关过滤器

现在让我们考虑一下我所说的“上下文敏感”过滤器,它由“按特定顺序排列的前 50%”之类的示例表示,例如按重量排列的前 50% 的小部件。 “上下文敏感”并不是最好的术语,但它是我目前所拥有的,并且它具有一定的描述性,因为它与到目前为止流中的元素数量相关。

我们如何使用流来实现这样的事情?除非有人想出一些非常聪明的东西,否则我认为我们必须首先在某个地方(例如,在列表中)收集元素,然后才能将第一个元素发送到输出。这有点像sorted()在管道中,在读取每个输入元素并对它们进行排序之前,无法判断哪个是第一个输出元素。

使用流查找按权重排名前 50% 的小部件的简单方法如下所示:

List<Widget> temp =
    list.stream()
        .sorted(comparing(Widget::weight).reversed())
        .collect(toList());
temp.stream()
    .limit((long)(temp.size() * 0.5))
    .forEach(System.out::println);

这并不复杂,但有点麻烦,因为我们必须将元素收集到列表中并将其分配给变量,以便在 50% 计算中使用列表的大小。

但这是有限制的,因为它是这种过滤的“静态”表示。我们如何将其链接到具有可变数量元素(其他过滤器或标准)的流中,就像我们对谓词所做的那样?

一个重要的观察是,该代码在流的消耗和流的发出之间完成其实际工作。它碰巧在中间有一个收集器,但是如果你将一个流链接到它的前端并将内容链接到它的后端,那么没有人会更明智。事实上,标准流管道操作就像map and filter每个都将一个流作为输入并发出一个流作为输出。所以我们可以自己写一个类似这样的函数:

Stream<Widget> top50PercentByWeight(Stream<Widget> stream) {
    List<Widget> temp =
        stream.sorted(comparing(Widget::weight).reversed())
              .collect(toList());
    return temp.stream()
               .limit((long)(temp.size() * 0.5));
}

类似的示例可能是找到最短的三个小部件:

Stream<Widget> shortestThree(Stream<Widget> stream) {
    return stream.sorted(comparing(Widget::length))
                 .limit(3);
}

现在我们可以编写一些将这些有状态过滤器与普通流操作结合起来的东西:

shortestThree(
    top50PercentByWeight(
        widgetList.stream()
                  .filter(w -> w.length() >= 10)))
.forEach(System.out::println);

这可行,但有点糟糕,因为它读起来是“从内到外”和向后的。流源是widgetList它通过普通谓词进行流式传输和过滤。现在,向后看,应用前 50% 的过滤器,然后应用最短的三个过滤器,最后应用流操作forEach应用于最后。这可行,但读起来很混乱。而且它仍然是静态的。我们真正想要的是有一种方法将这些新的过滤器放入我们可以操作的数据结构中,例如,运行所有排列,如原始问题中所示。

此时的一个关键见解是,这些新型过滤器实际上只是函数,并且 Java 中有函数式接口类型,它让我们可以将函数表示为对象、操作它们、将它们存储在数据结构中、组合它们等。接受某种类型的参数并返回相同类型的值的函数接口类型是UnaryOperator。本例中的参数和返回类型是Stream<Widget>。如果我们要采用方法参考,例如this::shortestThree or this::top50PercentByWeight,结果对象的类型将是

UnaryOperator<Stream<Widget>>

如果我们将它们放入一个列表中,该列表的类型将是

List<UnaryOperator<Stream<Widget>>>

啊!三层嵌套泛型对我来说太多了。 (但阿列克谢·希皮列夫 https://stackoverflow.com/users/2613885/aleksey-shipilev有一次向我展示了一些使用四层嵌套泛型的代码。)过多泛型的解决方案是定义我们自己的类型。让我们将我们的一项新事物称为“标准”。事实证明,让我们的新函数式接口类型与以下内容相关并没有什么价值UnaryOperator,所以我们的定义可以简单地是:

@FunctionalInterface
public interface Criterion {
    Stream<Widget> apply(Stream<Widget> s);
}

现在我们可以创建一个像这样的标准列表:

List<Criterion> criteria = Arrays.asList(
    this::shortestThree,
    this::lengthGreaterThan20
);

(我们将在下面弄清楚如何使用这个列表。)这是向前迈出的一步,因为我们现在可以动态地操作该列表,但它仍然有一定的限制。首先,它不能与普通谓词组合。其次,这里有很多硬编码值,例如最短的三个:两个或四个怎么样?与长度不同的标准怎么样?我们真正想要的是一个为我们创建这些 Criterion 对象的函数。这对于 lambda 来说很容易。

这将创建一个标准,在给定比较器的情况下选择前 N 个小部件:

Criterion topN(Comparator<Widget> cmp, long n) {
    return stream -> stream.sorted(cmp).limit(n);
}

这将创建一个标准,在给定比较器的情况下选择前 p% 的小部件:

Criterion topPercent(Comparator<Widget> cmp, double pct) {
    return stream -> {
        List<Widget> temp =
            stream.sorted(cmp).collect(toList());
        return temp.stream()
                   .limit((long)(temp.size() * pct));
    };
}

这从普通谓词创建了一个标准:

Criterion fromPredicate(Predicate<Widget> pred) {
    return stream -> stream.filter(pred);
}

现在我们有一种非常灵活的方法来创建标准并将它们放入列表中,其中可以对它们进行子集化或排列或其他方式:

List<Criterion> criteria = Arrays.asList(
    fromPredicate(w -> w.length() > 10),                    // longer than 10
    topN(comparing(Widget::length), 4L),                    // longest 4
    topPercent(comparing(Widget::weight).reversed(), 0.50)  // heaviest 50%
);

一旦我们有了 Criterion 对象的列表,我们就需要找到一种方法来应用所有这些对象。再次,我们可以使用我们的朋友reduce将它们全部组合成一个 Criterion 对象:

Criterion allCriteria =
    criteria.stream()
            .reduce(c -> c, (c1, c2) -> (s -> c2.apply(c1.apply(s))));

恒等函数c -> c很清楚,但第二个参数有点棘手。给定一个流s我们首先应用 Criterion c1,然后应用 Criterion c2,这被包装在一个 lambda 中,该 lambda 接受两个 Criterion 对象 c1 和 c2,并返回一个 lambda,该 lambda 将 c1 和 c2 的组合应用到流并返回结果流。

现在我们已经编写了所有标准,我们可以将其应用于小部件流,如下所示:

allCriteria.apply(widgetList.stream())
           .forEach(System.out::println);

这仍然有点由内而外,但控制得相当好。最重要的是,它解决了最初的问题,即如何动态组合标准。一旦标准对象处于数据结构中,就可以根据需要对它们进行选择、子集化、排列或任何其他操作,并且可以将它们全部组合在单个标准中并使用上述技术将其应用于流。

函数式编程大师可能会说“他刚刚重新发明了......!”这可能是真的。我确信这可能已经在某个地方被发明了,但它对于 Java 来说是新的,因为在 lambda 之前,编写使用这些技术的 Java 代码是不可行的。

更新2014-04-07

我已经清理并发布了完整的示例代码 https://gist.github.com/stuart-marks/10076102要点。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在Java 8中动态进行过滤? 的相关文章

随机推荐

  • 如何在使用 node_save($node) 创建节点时将图像附加到节点;

    您好 我正在使用 drupal 7 并尝试通过使用 php 解析 xml 来导入数据 然后使用以下命令创建节点node save node 到目前为止 我已经成功地从 xml 创建节点 无需任何图像 我想在导入图像时将图像附加到节点 我知道
  • 将值从页面传递到用户控件

    我将姓名和姓氏存储在主页的两个标签中 我在一个类中也有这些值 类没有做太多事情 但我将它们用于将来的扩展 我有一个用户控件 它将发送一封以姓名和姓氏作为正文的电子邮件 我的问题是如何将标签或类变量值传输到用户控件的主体变量中 使用要传递给它
  • 混合 16 位线性 PCM 流并避免削波/溢出

    我尝试将 2 个 16 位线性 PCM 音频流混合在一起 但似乎无法克服噪音问题 我认为它们是在将样品混合在一起时溢出的 我有以下功能 short int mix sample short int sample1 short int sam
  • CPU和GPU的区别

    CPU的单个处理单元和GPU的单个处理单元有什么区别 我在互联网上看到的大多数地方都涵盖了两者之间的高级差异 我想知道每条指令可以执行哪些指令 它们的速度有多快 以及这些处理单元如何集成到完整的架构中 这似乎是一个答案很长的问题 所以很多链
  • 在 spring jpa 实体中保存级联实体后缺少值

    我对 Spring 和 JPA 等比较陌生 我试图在标签和客户之间创建多对多关系 双向 我想我的关系是正确的 除了一个问题之外 一切都运行良好 保存到数据库后 Tag 值为 Null 所以我正在做的是 向客户添加新的标签列表 然后使用级联选
  • javascript中的子对象函数

    我知道您可以使用子对象和函数创建文字对象 var obj val 1 level1 val 2 val2 3 func function return this val2 console log obj val console log ob
  • 创建 Javascript 日历(完整,不弹出)

    我在创建 javascript 约会样式日历时遇到一些困难 虽然它确实进行了渲染 但我知道会有一种更有效的方法来实现它 有人有用于创建日历的模式吗 我将使用 jQuery 但我不想使用某人的日历插件 因为 a 我还没有找到适合我需要的插件
  • 修复 Google 地图折线的编码字符串

    我有一个编码字符串 我想用它在谷歌地图中画一条线 但由于反斜杠等特殊字符 Javascript 无法读取该字符串 不幸的是我对Javascript没有太多经验 而且我不知道如何转义这些字符 编码字符串 qikrIehwu WOKEIAIBG
  • MVC 和 JQuery:检索表单数据的最佳实践

    我有一些 JQuery 使用 Ajax 将信息发送回我的控制器进行处理 我这样做是这样的 Define my controls Get the values from my controls var param1 pName val Def
  • 零散的响应与浏览器响应不同

    我正在尝试用 scrapy 抓取此页面 http www barnesandnoble com s dref 4815 sort SA startat 7391 我得到的响应与我在浏览器中看到的不同 浏览器响应有正确的页面 而 scrapy
  • Angular2 Pipe 转换货币

    我创建了一个使用 api 转换货币的方法 如下所示 exchange Input string Output string value number number let inputRate this currencyStorage get
  • 休眠错误:当前事务被中止,命令被忽略直到事务块结束

    我随机在 catalina out 日志中看到相同的错误 WARNING SQL Error 0 SQLState 25P02 Sep 8 2010 11 50 13 PM org hibernate util JDBCExceptionR
  • Android 上的推送通知负载中的 gcm.notification.e=1 代表什么?

    我正在编写一个 Android 应用程序 它使用 Google Cloud Messaging 从服务器接收下游消息 当收到消息时onMessageReceived我打印了捆绑包并阅读了以下内容 RECEIVED PUSH NOTIFICA
  • 如何从 docker credStore 检索密码?

    我在用着docker credential desktop存储我的远程 docker 注册表的密码 据我了解 此命令应该检索我的注册表密码和用户名 docker credential desktop get https index dock
  • Web 应用程序加入 Azure Web Marketplace

    我们检查了这个文档 https blogs msdn microsoft com appserviceteam 2016 08 26 onboarding to azure web marketplace https blogs msdn
  • .Net XML 模式验证是否应该在到达第一个无效元素时停止?

    我加载了一个 XML 字符串和一个架构并将其传递到一个函数中 我让它根据模式正确验证 XML 但是它总是在第一个无效元素的范围内停止验证 无效数据 它会继续下去 无效 缺失属性 会继续下去 但无效元素 它会停止 并且不会在该范围内进一步验证
  • 如何强制元素保持在同一行

    嗨 我有以下 css div container height 20px overflow hidden margin 15px 0px padding 5px 10px 5px 10px white space nowrap div co
  • 如何获取类路径上的包和/或类的列表?

    在 Java 中 我可以使用 ClassLoader 来获取已加载的类的列表以及这些类的包 但是如何获取可以加载的类的列表 即位于类路径上的类的列表 与包裹相同 这是针对编译器的 在解析 foo bar Baz 时 我想知道 foo 是否是
  • 升级到 FirebaseUI 3.0 后无法使用 FirebaseRecyclerOptions 检索数据

    我在用着FirebaseRecyclerOptions因为我升级到了新的 FirebaseUI 3 0 版本 但现在我无法从数据库中检索任何内容 相同的代码在旧版本中运行得很好FirebaseRecylcerAdapter方法 好像根本进不
  • 如何在Java 8中动态进行过滤?

    我知道在 Java 8 中 我可以像这样进行过滤 List