适用人群 :Java 开发者,想了解函数式编程在实际项目中的应用 前置知识:了解 Java 8 Lambda 表达式基础
一、先来看一个实际场景
场景:同步 10000 条债券数据到接口平台
传统做法:
Java// 一次性查询所有数据List<BondQuotaQueryBO> allData = bondQuotaQueryService.queryAll(queryBO);// 逐条处理for (BondQuotaQueryBO bo : allData) { syncSingleQuota(bo, tenantId);}
问题:
-
❌ 10000 条数据一次性加载到内存,可能 OOM(内存溢出)
-
❌ 如果数据有 10 万条呢?
-
❌ 查询超时怎么办?
优雅做法:
Java// 分页查询,逐页处理PageQueryUtil.queryAndProcess( pageInfo -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), rows -> { for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); } }, 100 // 每页 100 条);
好处:
-
✅ 每次只加载 100 条到内存
-
✅ 自动处理所有分页
-
✅ 代码简洁,逻辑清晰
二、PageQueryUtil 的核心设计
1. 方法签名(记住这个公式)
Javapublic static <T> long queryAndProcess( Function<PageInfo, PageResult<T>> queryFunction, // ← 怎么查 Consumer<List<T>> pageHandler, // ← 怎么处理 int pageSize // ← 每页多少条)
通俗理解:
-
参数 1:你告诉它"怎么查数据"(给个查询函数)
-
参数 2:你告诉它"查到数据后怎么处理"(给个处理函数)
-
参数 3:每页查多少条
返回值:一共处理了多少条数据
三、实战演练:完整代码拆解
步骤 1:准备查询条件
JavaBondQuotaQueryBO queryBO = new BondQuotaQueryBO();queryBO.setLockStatus(2); // 已审批queryBO.setNotEndType(1); // 未终止
步骤 2:调用 queryAndProcess
Javalong total = PageQueryUtil.queryAndProcess( // 参数 1:查询函数(怎么查) pageInfo -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), // 参数 2:处理函数(怎么处理) rows -> { for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); } }, // 参数 3:每页条数 PAGE_SIZE // 100);
步骤 3:理解 Lambda 表达式
参数 1 详解:
JavapageInfo -> bondQuotaQueryService.queryBondQuotaQueryByPage(queryBO, pageInfo)
翻译成人话:
"给我一个分页信息(pageInfo),我返回一页数据给你"
等价于:
Javanew Function<PageInfo, PageResult<BondQuotaQueryBO>>() { @Override public PageResult<BondQuotaQueryBO> apply(PageInfo pageInfo) { return bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo); }}
关键点:
-
pageInfo 是 Lambda 的参数(占位符)
-
queryBO 是从外部捕获的变量(闭包)
-
这个 Lambda 现在不执行,只是定义了一个"规则"
参数 2 详解:
Javarows -> { for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); }}
翻译成人话:
"给我一页数据(rows),我逐条处理它们"
等价于:
Javanew Consumer<List<BondQuotaQueryBO>>() { @Override public void accept(List<BondQuotaQueryBO> rows) { for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); } }}
四、PageQueryUtil 内部是如何工作的?
执行流程图:
PlainTextPageQueryUtil.queryAndProcess()│├─ 【第 1 步】创建第 1 页的分页参数│ PageInfo pageInfo = new PageInfo(1, 100);│├─ 【第 2 步】调用你的查询函数│ PageResult<T> result = queryFunction.apply(pageInfo);│ ↑│ └─ 实际执行:bondQuotaQueryService.queryBondQuotaQueryByPage(queryBO, pageInfo)│├─ 【第 3 步】调用你的处理函数│ pageHandler.accept(result.getRows());│ ↑│ └─ 实际执行:for (BondQuotaQueryBO bo : rows) { syncSingleQuota(bo, tenantId); }│├─ 【第 4 步】获取总条数,计算总页数│ long total = result.getTotal(); // 比如 1000│ int totalPages = 1000 / 100 = 10;│├─ 【第 5 步】循环查询第 2~10 页│ for (int page = 2; page <= 10; page++) {│ PageInfo pageInfo = new PageInfo(page, 100);│ PageResult<T> result = queryFunction.apply(pageInfo); // 查询│ pageHandler.accept(result.getRows()); // 处理│ }│└─ 【第 6 步】返回总条数 return processedCount;
时序图:
PlainText调用方 PageQueryUtil 你的查询函数 你的处理函数 │ │ │ │ │──queryAndProcess──> │ │ │ │ │ │ │ │ │──apply(pageInfo)──> │ │ │ │ │ │ │ │<─PageResult 返回── │ │ │ │ │ │ │ │────────accept(rows) ──────────────────────────> │ │ │ │ │ │ │ (循环第 2~N 页...) │ │ │ │ │ │<─返回总条数───────── │ │ │ │ │ │ │
五、为什么需要 pageInfo ->?
常见疑惑:
"pageInfo 不是在 queryAndProcess 里面定义了吗?为什么 Lambda 还要写一次?"
答案:
Lambda 是在定义"函数",不是在"调用函数"!
类比理解:
场景:你点外卖
传统方式:
Java// 你直接去餐厅吃List<Food> allFood = restaurant.getAllFood();eat(allFood);
外卖方式(PageQueryUtil):
Java// 你告诉外卖平台:FoodDelivery.orderAndEat( // 参数 1:怎么取餐 box -> restaurant.getFoodByBox(box), // 参数 2:怎么吃 foods -> { for (Food food : foods) { eat(food); } }, // 参数 3:每箱装多少 10);
Lambda 的 box -> 就像:
"给我一个餐盒编号(box),我去取对应的菜"
外卖平台会:
-
准备第 1 个餐盒 → 调用你的取餐函数 → 送到你家 → 你开吃
-
准备第 2 个餐盒 → 调用你的取餐函数 → 送到你家 → 你开吃
-
...
关键: 餐盒编号是外卖平台准备的,你只需要定义"收到编号后怎么做"!
六、闭包:Lambda 的超能力
问题:
queryBO 没有作为参数传给 queryAndProcess,为什么查询函数里能用?
答案:
Lambda 可以"记住"定义时的上下文!
JavaBondQuotaQueryBO queryBO = new BondQuotaQueryBO(); // ← 外部变量queryBO.setLockStatus(2);PageQueryUtil.queryAndProcess( // Lambda"捕获"了外部的 queryBO pageInfo -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), // ↑ // └─ 外部变量,被 Lambda 捕获 ...);
通俗理解:
Lambda 就像一个" closures(闭包)",把定义时能看到的变量都装进包里,以后随时能用!
七、举一反三:更多使用场景
场景 1:批量发送邮件
JavaEmailQueryBO queryBO = new EmailQueryBO();queryBO.setStatus("PENDING");PageQueryUtil.queryAndProcess( pageInfo -> emailService.queryPendingEmails(queryBO, pageInfo), emails -> { for (Email email : emails) { sendEmail(email); } }, 50);
场景 2:导出 Excel
JavaPageQueryUtil.queryAndProcess( pageInfo -> orderService.queryOrders(queryBO, pageInfo), orders -> { // 逐页写入 Excel excelWriter.write(orders); }, 1000);
场景 3:数据清洗
JavaPageQueryUtil.queryAndProcess( pageInfo -> dataService.queryDirtyData(pageInfo), dataList -> { for (Data data : dataList) { cleanData(data); } }, 200);
八、常见错误避坑
❌ 错误 1:直接调用方法(没有 Lambda)
Java// 错误!pageInfo 未定义PageQueryUtil.queryAndProcess( bondQuotaQueryService.queryBondQuotaQueryByPage (queryBO, pageInfo), ...);
正确写法:
JavaPageQueryUtil.queryAndProcess( pageInfo -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), ...);
❌ 错误 2:忘记写 ->
Java// 错误!这不是 LambdaPageQueryUtil.queryAndProcess( (PageInfo pageInfo) bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), ...);
正确写法:
JavaPageQueryUtil.queryAndProcess( (PageInfo pageInfo) -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), ...);
❌ 错误 3:类型不匹配
Java// 错误!queryFunction 返回值类型不对PageQueryUtil.queryAndProcess( pageInfo -> bondQuotaQueryService.queryCount (queryBO), // ← 返回 Integer,不是 PageResult ...);
正确写法:
JavaPageQueryUtil.queryAndProcess( pageInfo -> bondQuotaQueryService. queryBondQuotaQueryByPage(queryBO, pageInfo), ...);
九、核心要点总结
1. 方法签名(背下来)
JavaqueryAndProcess( Function<PageInfo, PageResult<T>> queryFunction, // 怎么查 Consumer<List<T>> pageHandler, // 怎么处理 int pageSize // 每页多少条)
2. Lambda 表达式
Java// 查询函数:接收 pageInfo,返回 PageResultpageInfo -> service.query(queryBO, pageInfo)// 处理函数:接收 rows,没有返回值rows -> { for (T item : rows) { process(item); } }
3. 闭包特性
JavaSomeBO queryBO = new SomeBO(); // 外部变量pageInfo -> service.query(queryBO, pageInfo); // Lambda 捕获外部变量
4. 执行流程
PlainText1. 创建第 1 页 PageInfo2. 调用 queryFunction.apply(pageInfo) → 查询3. 调用 pageHandler.accept(rows) → 处理4. 循环第 2~N 页5. 返回总条数
十、课后练习
练习题 1:补全代码
Java// 需求:分页查询用户,每页 50 条,打印每个用户的名字UserQueryBO queryBO = new UserQueryBO();queryBO.setStatus("ACTIVE");PageQueryUtil.queryAndProcess( // 补全参数 1:查询函数 ________________________________, // 补全参数 2:处理函数 ________________________________, // 补全参数 3:每页条数 ____);
参考答案:
JavaPageQueryUtil.queryAndProcess( pageInfo -> userService.queryUsers(queryBO, pageInfo), users -> { for (User user : users) { System.out.println(user.getName()); } }, 50);
练习题 2:判断正误
Java// 这段代码有什么问题?PageQueryUtil.queryAndProcess( pageInfo -> { userService.queryUsers(queryBO, pageInfo); }, users -> System.out.println(users.size()), 100);
答案: 查询函数缺少 return 语句!应该改为:
JavapageInfo -> { return userService.queryUsers(queryBO, pageInfo);}
结语
PageQueryUtil.queryAndProcess 是模板方法模式 + 函数式编程的完美结合:
-
模板方法模式:PageQueryUtil 定义流程,你填充具体逻辑
-
函数式编程:用 Lambda 表达式传递行为,代码简洁优雅
掌握这个工具,让你的分页查询代码:
-
✅ 内存友好
-
✅ 逻辑清晰
-
✅ 易于复用
-
✅ 优雅简洁
记住这个公式:
PlainTextqueryAndProcess( 怎么查(Lambda), 怎么处理(Lambda), 每页多少条)
下次遇到分页查询,试试这个优雅的打开方式吧!🎉