Java函数式编程之【过滤器filter合并】【predicate(断言)】与【谓词逻辑】

  • 一、合并多个过滤器filter (Lambda版本)
  • 二、合并多个过滤器filter (谓词逻辑(Predicate)版本)
    • (一)使用基础谓词合并
    • (二)使用高级谓词合并
    • (三)使用带参数的可配置谓词合并
    • (四)使用复杂的谓词工厂合并

一、合并多个过滤器filter (Lambda版本)

合并多个filter()操作是一个非常重要且常见的优化策略。它的核心思想是:通过逻辑运算符将多个过滤条件合并为一个filter(),减少Stream流管道中的阶段数量和 predicate(断言)的执行次数。

经典示例:筛选用户列表
我们先定义一个User用户类:

package predicate;public class User {public enum Status { ACTIVE, INACTIVE, PENDING }private String name;private int age;private Status status;private int loginCount;public User(String name,int age,Status status,int n) {this.name = name;this.age = age;this.status = status;loginCount = n;}public String getName() {return name;}public int getAge() { return age; }public Status getStatus() { return status; }public int getLoginCount() { return loginCount; }
}

假设我们有一个User对象列表,我们需要筛选出同时满足以下条件的用户:

  • 年龄大于等于18岁(成年人)
  • 年龄小于65岁(未退休)
  • 账户状态是激活的(ACTIVE)
  • 登录次数大于0(活跃用户)

低效做法:使用多个连续的 filter 操作

public class PredicateDemo {private static List<User> users = new ArrayList<>(Arrays.asList(new User("张三", 32, Status.ACTIVE, 2),new User("李四", 16, Status.INACTIVE, 0),new User("王明", 72, Status.ACTIVE, 1),new User("王五", 35, Status.ACTIVE, 5),new User("钱多多", 68, Status.PENDING, 2)));public static void testA() {List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18)        // 第一轮过滤:检查条件1.filter(user -> user.getAge() < 65)         // 第二轮过滤:检查条件2.filter(user -> user.getStatus() == User.Status.ACTIVE) // 第三轮过滤:检查条件3.filter(user -> user.getLoginCount() > 0)   // 第四轮过滤:检查条件4.collect(Collectors.toList());}}

在这个做法中,数据仿佛要“过四道筛子”:

  • 第一个filter处理整个列表,生成一个包含所有成年人的新流。
  • 第二个filter处理上一个流,生成一个包含18-65岁用户的新流。
  • 第三个和第四个filter继续重复这个过程。

一个最终被淘汰的用户(例如,一个17岁的用户)可能只在第一轮过滤时就被排除了,但它仍然需要经历后续所有filter阶段的 predicate 检查吗?不,实际上不会,但它的处理流程仍然有开销。

高效做法:合并 filter 操作

	public static void testB() {List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18 && user.getAge() < 65&& user.getStatus() == User.Status.ACTIVE&& user.getLoginCount() > 0).collect(Collectors.toList());}

其实,在本例中还可以继续进行优化。
利用逻辑运算的短路(Short-Circuit)评估: 这也是一个关键的优化点。Java中的逻辑运算符 && (AND) 和 || (OR) 是短路的。

1)、对于 逻辑运算符 && (AND):如果第一个条件为 false,整个表达式的结果立即确定为 false,后续条件将不再被计算。
在本例中,如果一个用户年龄是17岁(user.getAge() >= 18 为 false),JVM会立即跳过对其退休状态、账户状态和登录次数的检查。这节省了3次方法调用和比较操作。

将最可能失败或计算成本最低的条件放在&&的最左边,可以最大化短路优化带来的收益。
合并后,条件的顺序变得很重要,因为它直接影响短路评估的效果。
进一步优化策略:

    • 将最可能使条件失败的条件放在前面。 如果90%的用户都是非活跃的,那么先检查loginCount > 0就能最快地淘汰大部分元素。
    • 将计算成本最低的条件放在前面。 比较年龄(基本类型比较)的成本远低于从数据库或网络获取状态(假设getStatus()很昂贵)。先进行廉价的检查,可以避免对即将被淘汰的元素执行昂贵的操作。

优化合并filter()操作的 最终版本

//优化合并filter()操作的最终版本
List<User> users = // ... 获取用户列表List<User> filteredUsers = users.stream().filter(user -> user.getLoginCount() > 0        // 成本低,可能淘汰很多用户&& user.getAge() >= 18                      // 成本低&& user.getAge() < 65                       // 成本低&& user.getStatus() == User.Status.ACTIVE)  // 可能成本最高(例如涉及数据库查询),放在最后

注意事项: 如果某个昂贵条件是独立的,无法通过短路避免,有时将其单独放在一个filter里并在前面使用更廉价的条件过滤可能更优,但这需要具体分析。通常,合并filter并利用短路特性来调整过滤条件顺序是最佳实践。

2)、对于 逻辑运算符 || (OR):如果第一个条件为 true,整个表达式立即为 true,后续条件不再计算。
请看一个 逻辑运算符 **|| (OR)**逻辑连接的filter() 操作合并为一个操作的示例:

// 多个filter() 合并 和 优化 示例List<User> users = // ... 获取用户列表
// 未合并为一个filter (OR 逻辑)的原始状态
List<User> filteredUsers = users.stream().filter(user -> user.getTotalSpending() > 1000).filter(user -> user.getYearsAsMember() > 5)...// 简单合并为一个filter (OR 逻辑)
List<User> filteredUsers = users.stream().filter(user -> user.getTotalSpending() > 1000 || user.getYearsAsMember() > 5)...// 优化后: 优化策略:将更可能为true或成本更低的条件放在||前面
List<User> filteredUsers = users.stream().filter(user -> user.getYearsAsMember() > 5 || user.getTotalSpending() > 1000)...

结论: 可将多个连续的、用 && (AND)|| (OR) 逻辑连接的filter() 操作合并为一个,并利用短路评估来排序条件,是减少不必要的计算、提升Stream性能的经典且高效的手段。在编写Stream代码时,应有意识地检查是否存在可合并的filter()操作。

二、合并多个过滤器filter (谓词逻辑(Predicate)版本)

使用谓词逻辑(Predicate)来重写过滤器filter()合并操作是一个非常优雅且强大的方式。它既保持了性能优势,又提升了代码的可读性、复用性和声明性。

(一)使用基础谓词合并

首先,我们定义各个独立的过滤条件作为 Predicate 对象。

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;// 定义独立的谓词
Predicate<User> isAdult = user -> user.getAge() >= 18;
Predicate<User> isNotRetired = user -> user.getAge() < 65;
Predicate<User> isActive = user -> user.getStatus() == User.Status.ACTIVE;
Predicate<User> hasLoggedIn = user -> user.getLoginCount() > 0;

现在,我们可以用多种方式组合这些谓词:

方式一:使用 and() 方法进行逻辑与组合
这是最直接和推荐的方式,非常清晰地表达了“所有条件都必须满足”的逻辑。

	// 独立的谓词用法:使用 and() 方法进行逻辑与组合public static void test01() {  /*** 独立的谓词用法 ***/// 定义独立的谓词Predicate<User> isAdult = user -> user.getAge() >= 18;Predicate<User> isNotRetired = user -> user.getAge() < 65;Predicate<User> isActive = user -> user.getStatus() == User.Status.ACTIVE;Predicate<User> hasLoggedIn = user -> user.getLoginCount() > 0;List<User> filteredUsers = users.stream().filter(isAdult.and(isNotRetired).and(isActive).and(hasLoggedIn)) // 将多个谓词用 AND 逻辑连接.collect(Collectors.toList());filteredUsers.forEach(user->System.out.println(user.getName()));}

方式二:使用静态组合方法
可以通过一个静态工具方法将组合逻辑封装起来,使流操作更加简洁。

	/*** 在一个工具类中定义组合谓词:使用静态组合方法可以通过一个静态工具方法将组合逻辑封装起来,使流操作更加简洁。***/public static void test02() {   /*** 使用静态组合方法 ***///在Stream中使用List<User> filteredUsers = users.stream().filter(UserPredicates.isEligibleActiveUser()).collect(Collectors.toList());filteredUsers.forEach(user->System.out.println(user.getName()));}

下面这个是示例中用到的静态工具方法:

package predicate;import java.util.function.Predicate;//在一个工具类中定义组合谓词
public class UserPredicates {// 定义谓词static Predicate<User> isAdult = user -> user.getAge() >= 18;static Predicate<User> isNotRetired = user -> user.getAge() < 65;static Predicate<User> isActive = user -> user.getStatus() == User.Status.ACTIVE;static Predicate<User> hasLoggedIn = user -> user.getLoginCount() > 0;public static Predicate<User> isEligibleActiveUser() {return isAdult.and(isNotRetired).and(isActive).and(hasLoggedIn);}
}

(二)使用高级谓词合并

高级谓词操作
谓词逻辑的强大之处在于它可以轻松处理更复杂的组合逻辑。

示例:定义“高价值用户”谓词

Predicate<User> isHighValue = user -> user.getTotalPurchases() > 1000;
Predicate<User> isLongTermMember = user -> user.getMembershipYears() >= 3;// 组合逻辑:要么是高价值用户,要么是长期会员的活跃成人用户
Predicate<User> isSpecialUser = isAdult.and(isActive).and(isHighValue.or(isLongTermMember)); // 嵌套 OR 逻辑List<User> specialUsers = users.stream().filter(isSpecialUser).collect(Collectors.toList());

示例:使用 negate() 进行逻辑非操作

	// 示例:使用 negate() 进行逻辑非操作public static void test04() {Predicate<User> isAdult = user -> user.getAge() >= 18;Predicate<User> isActive = user -> user.getStatus() == User.Status.ACTIVE;Predicate<User> isInactive = isActive.negate(); // 非活跃用户Predicate<User> isMinor = isAdult.negate(); // 未成年人// 获取非活跃的或未成年的用户List<User> excludedUsers = users.stream().filter(isInactive.or(isMinor)).collect(Collectors.toList());}

(三)使用带参数的可配置谓词合并

带参数的可配置谓词:如果需要动态条件,可以创建返回 Predicate 的方法。

	// 创建动态谓词生成方法/*** 创建返回 Predicate 的方法。 	***/public static Predicate<User> ageInRange(int min, int max) {return user -> user.getAge() >= min && user.getAge() <= max;}public static Predicate<User> minLoginCount(int threshold) {return user -> user.getLoginCount() >= threshold;}public static void test05() {Predicate<User> isActive = user -> user.getStatus() == User.Status.ACTIVE;// 使用动态谓词int minLogin = 5;int maxAge = 60;List<User> filteredUsers = users.stream().filter(ageInRange(25, maxAge).and(minLoginCount(minLogin)).and(isActive)).collect(Collectors.toList());filteredUsers.forEach(user->System.out.println(user.getName()));}

(四)使用复杂的谓词工厂合并

复杂的复合谓词工厂:对于非常复杂的业务逻辑,可以创建一个专门的谓词工厂类。

package predicate;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import predicate.User.Status;/*** 对于非常复杂的业务逻辑,可以创建一个专门的谓词工厂类。***/
public class UserPredicateFactory {private static List<User> users = new ArrayList<>(Arrays.asList(new User("张三", 32, Status.ACTIVE, 2),new User("李四", 16, Status.INACTIVE, 0),new User("王明", 72, Status.ACTIVE, 1),new User("王五", 35, Status.ACTIVE, 5),new User("钱多多", 68, Status.PENDING, 2)));public static Predicate<User> createEligibilityPredicate(boolean requireActive, int minAge, int minLogins,User.Status... allowedStatuses) {Predicate<User> predicate = user -> user.getAge() >= minAge;if (requireActive) {predicate = predicate.and(user -> user.getLoginCount() >= minLogins);}if (allowedStatuses != null && allowedStatuses.length > 0) {// allowedStatuses 是一个 User.Status 数组Set<User.Status> statusSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedStatuses)));predicate = predicate.and(user -> statusSet.contains(user.getStatus()));}return predicate;}public static void main(String[] args) {// 使用工厂创建谓词Predicate<User> customEligibility = UserPredicateFactory.createEligibilityPredicate(true, 18, 1, User.Status.ACTIVE, User.Status.PENDING);List<User> result = users.stream().filter(customEligibility).collect(Collectors.toList());result.forEach(user->System.out.println(user.getName()));}
}

总结:
为什么谓词逻辑方式更优秀?

  • 可复用性:单个谓词(如 isAdult)可以在代码库的多个地方重用。
  • 可读性:.and(), .or(), .negate() 等方法让组合逻辑变得非常清晰,就像书写数学逻辑表达式一样。
  • 可测试性:每个谓词都可以单独进行单元测试,组合逻辑也可以独立测试。
  • 可维护性:当业务规则变化时,只需要修改对应的谓词定义,而不用到处修改Stream管道。
  • 声明式编程:代码更专注于"做什么"而不是"怎么做",符合函数式编程 philosophy。
  • 保持性能优势:底层仍然使用短路评估,性能与直接使用 && 运算符相当。

谓词逻辑方式的唯一缺点是会创建更多的对象(每个Predicate实例),但在绝大多数应用场景中,这种开销相对于它带来的好处和Stream操作本身的开销来说是微不足道的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97622.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97622.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97622.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CentOS10安装RabbitMQ

1.下载资源 &#xff08;1&#xff09;下载erlang-rpm 注意&#xff1a;按照图片中的下载&#xff0c;用绿色三角形指向的是重点关注的。 网址&#xff1a; erlang-rpmhttps://github.com/rabbitmq/erlang-rpm/releases &#xff08;2&#xff09;下载rabbitmq-server 注…

JVM——八股文

1. JDK, JRE和JVM的关系JDK JRE Java开发工具JRE JVM Java核心类库JDK供Java程序开发人员开发软件&#xff0c;JRE供客户使用&#xff0c;只需要JVM运行环境即可。JVM运行的是class字节码&#xff0c;不仅能运行Java代码&#xff0c;还能运行其他语言&#xff0c;只要语言能…

骑行把带定期换,维乐 Skin Wrap 把带焕新骑行

在公路骑行的装备体系里&#xff0c;把带是最易被忽视却至关重要的“消耗品”。它是骑手手部与车身的直接连接&#xff0c;每一次转向、变速、刹车&#xff0c;都需通过把带传递力量与操控意图&#xff1b;同时&#xff0c;它还承担着吸汗、减震、保护车把的作用。可长期使用后…

LeetCode100-73矩阵置零

本文基于各个大佬的文章 上点关注下点赞&#xff0c;明天一定更灿烂&#xff01; 前言 Python基础好像会了又好像没会&#xff0c;所有我直接开始刷leetcode一边抄样例代码一边学习吧。本系列文章用来记录学习中的思考&#xff0c;写给自己看的&#xff0c;也欢迎大家在评论区指…

宁波市第八届网络安全大赛 -- Crypto -- WriteUp

宁波市第八届网络安全大赛 – Crypto – WriteUp Three-prime RSA task import gmpy2 from Crypto.Util.number import *from secret import flagp getPrime(512) q getPrime(512) r getPrime(512) n p * q * r random_num getPrime(28) D ((p q r) * random_num) % n …

大语言模型 (LLM) 与多模态大模型 (MLM)

文章目录概述&#xff1a;从“模型”到“大”模型1、大语言模型 (Large Language Model, LLM)1.1 定义与概述关键特征&#xff1a;1.2 核心技术与架构Transformer架构自注意力机制 (Self-Attention)1.3 训练过程1.4 工作原理2. 多模态大模型 (Multimodal Large Model, MLM)2.1 …

HTML应用指南:利用GET请求获取全国招商银行网点位置信息

招商银行&#xff08;China Merchants Bank, CMB&#xff09;作为中国领先的股份制商业银行&#xff0c;始终坚持“以客户为中心”的服务理念&#xff0c;致力于为个人客户、企业客户及机构客户提供专业、高效、便捷的综合金融服务。依托“轻型银行”战略与“金融科技银行”建设…

JVM性能监控工具的使用

了解JVM性能监控工具并能熟练使用&#xff0c;是Java开发者进阶的必备技能。下面本文将为你介绍一些主流的JVM性能监控工具及其使用方法&#xff0c;并通过一些场景案例来分析如何应用这些工具解决实际问题。 &#x1f6e0;️ JVM性能监控与调优工具指南 ✨ 工具概览 以下是几款…

【工作】一些找工作需要了解避雷的知识

面试前 1.公司的具体情况 公司全称&#xff0c;办公地点&#xff0c;涉及岗位 要求hr做个简单的公司介绍 2.岗位职责/业务方向 工作内容、公司业务 3.薪资待遇&#xff0c;构成&#xff0c;底薪&#xff0c;五险一金 问一下工资范围 底薪 &#xff08;有责&#xff0c;无…

五、练习2:Git分支操作

练习2&#xff1a;Git分支操作 练习目标 掌握Git分支的创建、切换、合并等操作&#xff0c;理解分支在开发中的作用。 练习步骤 步骤1&#xff1a;准备基础仓库 # 创建练习目录 mkdir branch-practice cd branch-practice# 初始化仓库 git init# 创建初始文件 echo "# 分支…

【笔记】算法设计:异或空间线性基

Content1.什么是异或&#xff08;定义和性质&#xff09;2.异或空间线性基的构造方法3.异或空间线性基的应用4.算法设计例举5.小结说明算法设计应用之前&#xff0c;首先明确异或空间线性基&#xff1a;一种数据结构。用于处理异或关系&#xff08;运算&#xff09;下的向量空间…

Filebeat采集数据与日志分析实战

&#x1f31f;Filebeat采集数据的原理 Filebeat默认按行采集数据&#xff0c;如果数据没有换行&#xff0c;则该条数据无法采集到 属于有状态服务&#xff0c;可以记录上一次采集数据的位置点信息 修改配置文件 vim /etc/filebeat/config/03-log-to-console.yaml filebeat.inp…

Fluent Bit针对kafka心跳重连机制详解(下)

#作者&#xff1a;程宏斌 文章目录disconnectreconnect接上篇&#xff1a;https://blog.csdn.net/qq_40477248/article/details/150957571?spm1001.2014.3001.5501disconnect 断开连接的情况主要是两种: 连接或传输过程中有错误发生 超时, 比如空闲时间超时 ** * Close and …

React 第七十一节 Router中generatePath的使用详解及注意事项

前言 generatePath 是 React Router 的一个实用工具函数&#xff0c;用于根据路径模式和参数对象生成实际的 URL 路径。它在需要动态构建链接的场景中非常有用&#xff0c;比如生成导航链接或重定向路径。 1、基本用法和注意事项 import { generatePath } from react-router-do…

Python 爬虫案例:爬取豆瓣电影 Top250 数据

一、案例背景与目标 豆瓣电影 Top250 是国内权威的电影评分榜单之一&#xff0c;包含电影名称、评分、评价人数、导演、主演、上映年份、国家 / 地区、类型等关键信息。本案例将使用 Python 编写爬虫&#xff0c;实现以下目标&#xff1a; 自动请求豆瓣电影 Top250 的 10 个分…

SPA安全警示:OAuth2.0致命漏洞

OAuth2.0在SPA应用中的安全陷阱SPA&#xff08;单页应用&#xff09;通常采用隐式授权&#xff08;Implicit Flow&#xff09;或PKCE&#xff08;Proof Key for Code Exchange&#xff09;授权模式&#xff0c;但存在以下安全隐患&#xff1a;隐式授权模式的漏洞访问令牌直接暴…

table表格字段明细展示

文章目录1、字段渲染2、异步请求展示明细3、hover展示问题3.1 基本逻辑3.2 hover时长判断3.3 renderhover表格字段明细展示&#xff0c;属于比较小的需求&#xff0c;但是也有一定交互细节&#xff0c;本文选取部分场景。 1、字段渲染 render和渲染组件是有区别的。render常见为…

主网上线后生态极速扩张的 Berachain 生态,有哪些值得关注的项目?

Berachain 是典型的将 DeFi 思维嵌入到共识机制中的 Layer1&#xff0c;其核心是 PoL&#xff08;Proof of Liquidity&#xff09;共识。PoL 要求验证者在获得区块奖励前&#xff0c;必须将流动性导入白名单协议&#xff0c;并由市场决定资金流向。这样&#xff0c;验证者的权重…

claude-code对比GitHub-Copilot

Claude Code 文档日期&#xff1a;2025 年 08 月 20 日 定位 项目级开发助手&#xff0c;专注于全局视野和复杂任务的处理。 特点 超长上下文支持&#xff1a;支持 200k 超长上下文&#xff0c;适合处理复杂项目。丰富的自定义命令&#xff1a;提供灵活的命令配置&#xff0c;满…

Roo Code自定义Mode(模式)

什么是自定义模式&#xff1f; 简单来说&#xff0c;自定义模式就像是给Roo Code穿上不同的"职业装"。你可以创建针对特定任务或工作流程量身定制的模式&#xff0c;让Roo在不同场景下表现出专业的行为。 这些模式分为两种类型&#xff1a;全局模式&#xff08;在所有…