利用 Function 接口告别冗余(屎山)代码
前言
在 Java 开发的征途中,我们时常与重复代码不期而遇。这些重复代码不仅让项目显得笨重,更增加了维护成本。幸运的是,Java 8 带来了函数式编程的春风,以 Function 接口为代表的一系列新特性,为我们提供了破除这一难题的利剑。本文将以一个实际应用场景为例,即使用 Java 8 的函数式编程特性来重构数据有效性断言逻辑,展示如何通过 SFunction(基于 Java 8 的 Lambda 表达式封装)减少代码重复,从而提升代码的优雅性和可维护性。
背景故事
数据校验的烦恼想象一下,在一个复杂的业务系统中,我们可能需要频繁地验证数据库中某个字段值是否有效,是否符合预期值。传统的做法可能充斥着大量相似的查询逻辑,每次都需要手动构建查询条件、执行查询并处理结果,这样的代码既冗长又难以维护。例如以下两个验证用户 ID 和部门 ID 是否有效的方法,虽然简单,但每次需要校验不同实体或不同条件时,就需要复制粘贴并做相应修改,导致代码库中充满了大量雷同的校验逻辑,给维护带来了困扰。
// 判断用户 ID 是否有效 public void checkUserExistence(String userId) { User user = userDao.findById(userId); if (user == null) { throw new RuntimeException("用户ID无效"); } } // 判断部门 ID 是否有效 public void checkDeptExistence(String deptId) { Dept dept = deptDao.findById(deptId); if (dept == null) { throw new RuntimeException("部门ID无效"); } }
Java 8 的魔法棒
函数式接口Java 8 引入了函数式接口的概念,其中 Function<T, R> 是最基础的代表,它接受一个类型 T 的输入,返回类型 R 的结果。而在 MyBatis Plus 等框架中常用的 SFunction 是对 Lambda 表达式的进一步封装,使得我们可以更加灵活地操作实体类的属性。
实战演练
重构断言方法下面的 ensureColumnValueValid 方法正是利用了函数式接口的魅力,实现了对任意实体类指定列值的有效性断言:
/** * 确认数据库字段值有效(通用) * * @param <V> 待验证值的类型 * @param valueToCheck 待验证的值 * @param columnExtractor 实体类属性提取函数 * @param queryExecutor 单条数据查询执行器 * @param errorMessage 异常提示信息模板 */ public static <T, R, V> void ensureColumnValueValid(V valueToCheck, SFunction<T, R> columnExtractor, SFunction<LambdaQueryWrapper<T>, T> queryExecutor, String errorMessage) { if (valueToCheck == null) return; LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>(); wrapper.select(columnExtractor); wrapper.eq(columnExtractor, valueToCheck); wrapper.last("LIMIT 1"); T entity = queryExecutor.apply(wrapper); R columnValue = columnExtractor.apply(entity); if (entity == null || columnValue == null) throw new DataValidationException(String.format(errorMessage, valueToCheck)); }
改造后使用如下代码所示:
public void assignTaskToUser(AddOrderDTO dto) { ensureColumnValueValid(dto.getUserId(), User::getId, userDao::getOne, "用户ID无效"); ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效"); ensureColumnValueValid(dto.getCustomerId(), Customer::getId, customerDao::getOne, "客户ID无效"); ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效"); ensureColumnValueValid(dto.getSupplieId(), Supplie::getId, supplierDao::getOne, "供应商ID无效"); // 现在可以确信客户存在 Customer cus = customerDao.findById(dto.getCustomerId()); // 创建订单的逻辑... }
再如下例子:
// 优化前
public void validateUser(User user) { if (user.getName() == null || user.getName().isEmpty()) { throw new IllegalArgumentException("Name cannot be empty"); } if (user.getAge() < 18) { throw new IllegalArgumentException("Age must be at least 18"); } if (user.getEmail() == null || !user.getEmail().matches("regex")) { throw new IllegalArgumentException("Invalid email address"); } } // 优化后 public static <T> void validate(T obj, Function<T, Boolean> validator, String errorMessage) { if (!validator.apply(obj)) { throw new IllegalArgumentException(errorMessage); } } public static void validateUser2(User user) { validate(user, u -> u.getName() != null && !u.getName().isEmpty(), "Name cannot be empty"); validate(user, u -> u.getAge() >= 18, "Age must be at least 18"); validate(user, u -> u.getEmail() != null && u.getEmail().matches("regex"), "Invalid email address"); }
郭慕荣博客园