Java Lambda 表达式与函数式接口完全指南:从 CRUD 业务到 JDK 11/17 实战
一、为什么 Lambda 在 CRUD 业务中如此重要
在 Java 8 之前,处理集合数据、构建数据库查询条件、编写回调逻辑,几乎都离不开匿名内部类和冗长的 for 循环。以最简单的“从用户列表中筛选出指定状态的用户”为例:
// Java 7 写法
List<User> activeUsers = new ArrayList<>();
for (User user : userList) {
if (user.getStatus() == 1) {
activeUsers.add(user);
}
}Java 8 引入 Lambda 表达式和 Stream API 后,同一段逻辑可以压缩为:
List<User> activeUsers = userList.stream()
.filter(u -> u.getStatus() == 1)
.collect(Collectors.toList());代码量减少了约 70%,意图更加清晰。而真正让 Lambda 在 CRUD 业务中“不可或缺”的,是它与函数式接口的组合——这使得行为参数化成为可能,增删改查的每一个环节都可以用声明式的风格来表达。
Lambda 表达式的本质:它是一个匿名函数,用来实现函数式接口的唯一抽象方法。语法形式为 (参数) -> { 方法体 },根据方法体的复杂度可以简写为 (参数) -> 表达式。
函数式接口:只有一个抽象方法的接口(可以用 @FunctionalInterface 标注)。Lambda 表达式必须有“插槽”来接住它,这个插槽就是函数式接口。
下面用一个统一的 User 实体贯穿全文,所有示例都围绕它展开:
public class User {
private Long id;
private String username;
private Integer age;
private Integer status; // 1=正常, 2=冻结
private BigDecimal balance;
// 构造器、getter/setter 省略
}二、四大核心函数式接口:CRUD 的原子积木
Java 8 在 java.util.function 包中提供了大量函数式接口,其中Function、Predicate、Consumer、Supplier 是使用频率最高的四个。理解它们的“输入输出契约”,是写出干净 Lambda 代码的前提。
2.1 Function<T, R>——转换型接口
签名:R apply(T t)——接收一个 T 类型参数,返回一个 R 类型结果。
CRUD 场景:实体到 DTO 的转换、字段提取。
// 场景:将 User 对象转换为“用户名 + 年龄”的展示字符串
Function<User, String> toDisplay = user ->
user.getUsername() + " (" + user.getAge() + "岁)";
// 用于列表转换
List<User> users = Arrays.asList(
new User(1L, "张三", 25, 1, new BigDecimal("5000")),
new User(2L, "李四", 30, 2, new BigDecimal("8000"))
);
List<String> displays = users.stream()
.map(toDisplay) // 直接使用方法引用更简洁:User::toDisplayString
.collect(Collectors.toList());
// 结果:["张三 (25岁)", "李四 (30岁)"]
// Function 的链式组合:先提取用户名,再转大写
Function<User, String> usernameUpper = ((Function<User, String>) User::getUsername)
.andThen(String::toUpperCase);
String result = usernameUpper.apply(users.get(0)); // "张三".toUpperCase()关键点:Function 的默认方法 andThen 和 compose 允许将多个转换函数串联成流水线。在 CRUD 中,这常用于“查询 → 转换 → 输出”的链路。
2.2 Predicate<T>——断定型接口
签名:boolean test(T t)——接收一个 T 类型参数,返回布尔值。
CRUD 场景:查询过滤条件、删除前的安全检查、更新前的校验。
// 场景:组合多个查询条件
Predicate<User> isActive = user -> user.getStatus() == 1;
Predicate<User> isAdult = user -> user.getAge() >= 18;
Predicate<User> hasBalance = user -> user.getBalance().compareTo(BigDecimal.ZERO) > 0;
// 组合:状态正常 且 成年 且 余额大于0
Predicate<User> canTransact = isActive.and(isAdult).and(hasBalance);
// 在查询中使用
List<User> eligible = users.stream()
.filter(canTransact)
.collect(Collectors.toList());
// 在删除前使用:只有冻结状态的用户才允许删除
Predicate<User> canDelete = user -> user.getStatus() == 2;
// 在更新前使用:只有余额充足才执行扣减
Predicate<User> hasSufficientBalance = user ->
user.getBalance().compareTo(new BigDecimal("100")) >= 0;默认方法:and、or、negate 三个方法使得条件组合变得极其灵活,这在构建动态查询条件时非常实用。
2.3 Consumer<T>——消费型接口
签名:void accept(T t)——接收一个 T 类型参数,无返回值。
CRUD 场景:遍历打印、日志记录、更新前的字段填充、批量操作中的副作用。
// 场景:批量更新用户状态,同时记录日志
Consumer<User> freezeUser = user -> {
user.setStatus(2);
log.info("用户 [{}] 已被冻结", user.getUsername());
};
// 批量处理
users.stream()
.filter(user -> user.getBalance().compareTo(new BigDecimal("1000")) < 0)
.forEach(freezeUser);
// Consumer 的链式组合:先记录日志,再更新字段
Consumer<User> auditAndUpdate = ((Consumer<User>) user ->
log.info("准备更新用户: {}", user.getUsername()))
.andThen(user -> user.setStatus(1));注意:Consumer 的方法体通常包含副作用(side effect),在 Stream 操作中应谨慎使用 forEach,优先考虑用 map 生成新集合来保持函数式的纯度。
2.4 Supplier<T>——供给型接口
签名:T get()——无参数,返回一个 T 类型结果。
CRUD 场景:对象工厂、默认值提供、延迟初始化。
// 场景:创建默认的“访客用户”模板
Supplier<User> guestSupplier = () -> {
User guest = new User();
guest.setUsername("guest");
guest.setStatus(1);
guest.setBalance(BigDecimal.ZERO);
return guest;
};
// 使用:每次调用生成一个新的实例
User newGuest = guestSupplier.get();
// 在 CRUD 中的典型用法:为查询结果提供默认值
Supplier<List<User>> emptyListSupplier = ArrayList::new;
List<User> results = Optional.ofNullable(rawResults)
.orElseGet(emptyListSupplier);2.5 四大接口对照速查
| 接口 | 输入 | 输出 | 核心方法 | CRUD 典型用途 |
|---|---|---|---|---|
| Function<T,R> | T | R | apply | 实体→DTO 转换、字段提取 |
| Predicate<T> | T | boolean | test | 查询条件、删除/更新前校验 |
| Consumer<T> | T | void | accept | 遍历处理、日志记录、批量更新 |
| Supplier<T> | 无 | T | get | 对象工厂、默认值、延迟初始化 |
此外,BiFunction<T,U,R>(两个输入一个输出)、BiConsumer<T,U>(两个输入无输出)、UnaryOperator<T>(T→T,如 str -> str.toUpperCase())、BinaryOperator<T>(T,T→T,如 (a,b) -> a+b)也是 CRUD 中常用的变体。
三、Collection 新增的 Lambda 友好方法:CRUD 的直接支持
Java 8 为 Collection 体系新增了一批接受函数式接口的方法,这些方法是 CRUD 操作最直接的 Lambda 入口。
3.1 查询(Read)
Collection.forEach(Consumer):遍历集合。
users.forEach(user ->
System.out.println(user.getId() + ": " + user.getUsername()));Collection.stream():获取流,配合 filter/map/sorted 实现任意复杂查询。
// 组合查询:状态正常且余额大于5000,按余额降序,取前3个用户名
List<String> topNames = users.stream()
.filter(u -> u.getStatus() == 1)
.filter(u -> u.getBalance().compareTo(new BigDecimal("5000")) > 0)
.sorted(Comparator.comparing(User::getBalance).reversed())
.limit(3)
.map(User::getUsername)
.collect(Collectors.toList());Stream.filter(Predicate):条件过滤,是“查”的核心操作。Stream.map(Function):字段提取或类型转换。Stream.sorted(Comparator):排序,Comparator.comparing 配合 Lambda 非常简洁。
3.2 增加(Create)
// 使用 Supplier 批量生成
Supplier<User> defaultUser = () -> {
User u = new User();
u.setStatus(1);
u.setBalance(BigDecimal.ZERO);
return u;
};
List<User> newUsers = IntStream.range(0, 10)
.mapToObj(i -> {
User u = defaultUser.get(); // 每次获取新实例
u.setUsername("user_" + i);
return u;
})
.collect(Collectors.toList());3.3 修改(Update)
Collection.replaceAll(UnaryOperator<E>):原地替换所有元素。
// 将所有用户的余额统一增加 1000
users.replaceAll(user -> {
user.setBalance(user.getBalance().add(new BigDecimal("1000")));
return user;
});List.sort(Comparator):原地排序,Comparator 用 Lambda 表达。
users.sort(Comparator.comparing(User::getAge).thenComparing(User::getUsername));Map.replaceAll(BiFunction<K,V,V>):原地替换 Map 中的所有值
Map<Long, User> userMap = new HashMap<>();
userMap.replaceAll((id, user) -> {
user.setStatus(1); // 所有用户恢复为正常状态
return user;
});3.4 删除(Delete)
Collection.removeIf(Predicate):按条件删除元素,是“删”的最佳 Lambda 写法。
// 删除所有冻结状态的用户
users.removeIf(user -> user.getStatus() == 2);
// 组合条件删除:余额为0且状态为冻结
users.removeIf(user -> user.getStatus() == 2
&& user.getBalance().compareTo(BigDecimal.ZERO) == 0);四、方法引用:Lambda 的极致简写
方法引用是 Lambda 表达式的语法糖,当 Lambda 体只是“调用一个已有方法”时,可以用 :: 进一步简化。
| 类型 | Lambda 写法 | 方法引用 | CRUD 场景 |
|---|---|---|---|
| 静态方法 | s -> Integer.parseInt(s) | Integer::parseInt | 类型转换 |
| 实例方法(特定对象) | s -> System.out.println(s) | System.out::println | 打印/日志 |
| 实例方法(任意对象) | u -> u.getUsername() | User::getUsername | 字段提取 |
| 构造器 | () -> new User() | User::new | 对象工厂 |
// 用方法引用替代 Lambda
users.stream().map(User::getUsername).collect(Collectors.toList());
users.forEach(System.out::println);
users.removeIf(User::isFrozen); // 假设 isFrozen() 返回 boolean
// 构造器引用 + Supplier
Supplier<User> userFactory = User::new;选择原则:如果 Lambda 体只是调用一个已有方法,优先用方法引用,可读性更好。
五、数据库 CRUD 中的 Lambda:MyBatis-Plus 的 LambdaQueryWrapper
在实际企业开发中,Lambda 在 CRUD 中最大的“战场”是条件构造器。MyBatis-Plus 提供了 LambdaQueryWrapper 和 LambdaUpdateWrapper,用方法引用代替字符串字段名,实现了类型安全的动态 SQL 构建。
5.1 为什么要用 LambdaQueryWrapper
传统 QueryWrapper 需要硬编码数据库字段名:
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "张三"); // "username" 是字符串,拼错不会报错LambdaQueryWrapper 用方法引用:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三"); // 字段名错误在编译期就会暴露核心优势:编译期类型安全、重构友好(字段改名时 IDE 可自动更新)、无需记忆数据库字段名。
5.2 查询(Read)
// 服务层 Lambda 查询(IService 风格)
public List<User> queryUserList(String name, Integer status,
BigDecimal minBalance, BigDecimal maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name) // 条件为 null 时自动跳过
.eq(status != null, User::getStatus, status)
.between(minBalance != null && maxBalance != null,
User::getBalance, minBalance, maxBalance)
.orderByDesc(User::getBalance)
.list();
}关键机制:每个条件方法都接受一个 boolean condition 作为第一个参数。当条件为 false 时,该条件不会拼接到 SQL 中,实现了动态 SQL 的优雅表达。
等值查询与模糊查询:
wrapper.eq(User::getStatus, 1); // status = 1
wrapper.like(User::getUsername, "张"); // username LIKE '%张%'
wrapper.likeRight(User::getUsername, "张"); // username LIKE '张%'
wrapper.in(User::getId, Arrays.asList(1L, 2L, 3L)); // id IN (1,2,3)
wrapper.isNull(User::getBalance); // balance IS NULL5.3 更新(Update)
// 扣减余额,扣减后为0则冻结
public void updateUserBalance(Long id, String username, BigDecimal amount) {
lambdaUpdate()
.setSql("balance = balance - " + amount) // 原生 SQL 表达式
.set(amount.compareTo(BigDecimal.ZERO) == 0,
User::getStatus, 2) // 条件为 true 时才 SET
.eq(id != null, User::getId, id)
.eq(username != null, User::getUsername, username)
.update();
}setSql 允许直接写入 SQL 片段(如 balance - amount),适合算术运算;set 的 condition 参数实现了“条件更新”。
5.4 删除(Delete)
// 按条件删除
lambdaUpdate()
.eq(User::getStatus, 2)
.remove(); // 逻辑删除或物理删除取决于配置
// 使用 Mapper 的 Lambda 删除
LambdaQueryWrapper<User> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(User::getStatus, 2)
.lt(User::getBalance, new BigDecimal("100"));
userMapper.delete(deleteWrapper);5.5 新增(Create)
新增本身不涉及条件构造,但 Lambda 在批量新增中用于数据准备:
// 批量新增的 Lambda 数据生成
List<User> batch = IntStream.range(0, 10000)
.mapToObj(i -> {
User u = new User();
u.setUsername("batch_user_" + i);
u.setStatus(1);
u.setBalance(new BigDecimal("1000"));
return u;
})
.collect(Collectors.toList());
saveBatch(batch);六、Stream 链式操作在 CRUD 中的完整实战
Stream API 将查询、过滤、转换、收集串联成一条声明式管道。理解“中间操作”与“终端操作”的分界是关键:filter、map、sorted、limit、distinct 是中间操作(惰性),collect、forEach、count、findFirst、reduce 是终端操作(触发执行)。
6.1 查询:多条件筛选 + 排序 + 分页
public List<User> searchUsers(List<User> all, UserQuery query) {
return all.stream()
.filter(u -> query.getName() == null
|| u.getUsername().contains(query.getName()))
.filter(u -> query.getStatus() == null
|| u.getStatus().equals(query.getStatus()))
.filter(u -> query.getMinAge() == null
|| u.getAge() >= query.getMinAge())
.sorted(Comparator.comparing(User::getAge)
.thenComparing(User::getUsername))
.skip((long) query.getPage() * query.getSize()) // 分页:跳过
.limit(query.getSize()) // 分页:取多少
.collect(Collectors.toList());
}6.2 聚合与统计
// 按状态分组统计
Map<Integer, List<User>> byStatus = users.stream()
.collect(Collectors.groupingBy(User::getStatus));
// 按状态分组,再统计每组余额之和
Map<Integer, BigDecimal> balanceByStatus = users.stream()
.collect(Collectors.groupingBy(
User::getStatus,
Collectors.reducing(
BigDecimal.ZERO,
User::getBalance,
BigDecimal::add
)
));
// 取年龄最大的用户
Optional<User> oldest = users.stream()
.max(Comparator.comparing(User::getAge));
// 是否存在冻结用户
boolean hasFrozen = users.stream()
.anyMatch(u -> u.getStatus() == 2);6.3 去重与集合转换
// 提取用户名去重
List<String> distinctNames = users.stream()
.map(User::getUsername)
.distinct()
.collect(Collectors.toList());
// 转换为 Map:id -> User
Map<Long, User> idMap = users.stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
// 转换为 Map 并处理 key 冲突
Map<String, User> nameMap = users.stream()
.collect(Collectors.toMap(
User::getUsername,
Function.identity(),
(existing, replacement) -> existing // 保留先出现的
));七、JDK 11 对 Lambda 的增强
7.1 Lambda 参数使用 var(JEP 323)
Java 11 允许在 Lambda 参数中使用 var,这本身不会改变类型推断的行为,但使得在 Lambda 参数上添加注解成为可能——以前必须在参数前写出完整类型名,现在可以用 var 占位。
// Java 11 之前:Lambda 参数无法添加注解(必须写出完整类型)
// Java 11:使用 var 后可以添加注解
list.stream()
.map((@NotNull var user) -> user.getUsername().toUpperCase())
.collect(Collectors.toList());这个特性在需要标注 @Nullable、@NotNull 等校验注解时非常有用,尤其在配合框架的校验机制时。
7.2 其他与 Lambda 间接相关的实用增强
String 新增方法:isBlank()、lines()、strip()、repeat(n) 等,在 CRUD 中处理文本字段时非常实用。
// isBlank:判断空白字符串,比 trim().isEmpty() 更准确
list.stream()
.filter(u -> u.getUsername() != null && !u.getUsername().isBlank())
.collect(Collectors.toList());
// lines:将多行字符串转为 Stream
String multiLine = "user1\nuser2\nuser3";
List<String> names = multiLine.lines()
.filter(s -> !s.isBlank())
.collect(Collectors.toList());HTTP Client 标准化(JEP 321):Java 11 将 HTTP Client API 转为正式特性,配合 CompletableFuture 的异步回调天然适合 Lambda。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.GET()
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(body -> log.info("响应: {}", body)); // Lambda 作为回调八、JDK 17 对 Lambda 的增强与协同特性
JDK 17 作为 LTS 版本,虽然没有直接修改 Lambda 语法,但引入了几个与 Lambda 配合极佳的特性,能够显著提升 CRUD 代码的表达力。
8.1 Record 类(JEP 395)——数据载体的极简化
Record 是“透明数据载体”的紧凑写法,替代了传统 CRUD 中大量编写的 DTO/VO/实体类样板代码。
// 传统 DTO:需要构造器、getter、equals、hashCode、toString
// Record 写法:一行完成
public record UserDTO(Long id, String username, Integer age, String statusDesc) {}
// CRUD 中的使用:从实体转换为 DTO
List<UserDTO> dtos = users.stream()
.map(u -> new UserDTO(
u.getId(),
u.getUsername(),
u.getAge(),
u.getStatus() == 1 ? "正常" : "冻结"
))
.collect(Collectors.toList());
// Record 作为查询参数容器
public record UserQuery(String name, Integer status, BigDecimal minBalance) {}
// 使用
public List<User> search(UserQuery query) {
return users.stream()
.filter(u -> query.name() == null
|| u.getUsername().contains(query.name()))
.filter(u -> query.status() == null
|| u.getStatus().equals(query.status()))
.collect(Collectors.toList());
}Record 的关键约束:字段不可变(final),自动生成 equals/hashCode/toString,不可继承。这使它天然适合作为 CRUD 中的查询参数、API 响应、值对象。
8.2 密封类(JEP 409)——限定继承的精准控制
密封类允许一个类或接口精确指定哪些类可以继承/实现它,配合 switch 模式匹配可以实现穷尽性的类型分发。
// 定义 CRUD 操作结果的密封接口
public sealed interface OperationResult permits Success, Failure, NotFound {}
public record Success<T>(T data) implements OperationResult {}
public record Failure(String message, int code) implements OperationResult {}
public record NotFound(Long id) implements OperationResult {}
// 使用 switch 模式匹配分发(JDK 17 预览特性,需启用 --enable-preview)
public String handleResult(OperationResult result) {
return switch (result) {
case Success<?> s -> "操作成功: " + s.data();
case Failure f -> "操作失败[" + f.code() + "]: " + f.message();
case NotFound n -> "资源不存在: id=" + n.id();
};
}密封类 + Record + switch 模式匹配的组合,在 CRUD 中用于统一结果处理非常优雅,编译期即可确保所有情况被覆盖。
8.3 Switch 表达式(JEP 361)——更简洁的条件分发
Switch 表达式用 -> 替代 :,消除了贯穿语义,并且可以返回值。
// 根据用户状态返回描述
public String getStatusDesc(Integer status) {
return switch (status) {
case 1 -> "正常";
case 2 -> "冻结";
case 3 -> "注销";
default -> "未知";
};
}
// 在 Stream 中使用 switch 表达式
List<String> statuses = users.stream()
.map(u -> switch (u.getStatus()) {
case 1 -> "正常";
case 2 -> "冻结";
default -> "其他";
})
.collect(Collectors.toList());8.4 文本块(JEP 378)——多行字符串的自然写法
在 CRUD 中,文本块最适合编写内嵌的 SQL 或 JSON 模板。
// 传统写法:转义 + 字符串拼接
String sql = "SELECT id, username, age, status, balance\n" +
"FROM user\n" +
"WHERE status = 1\n" +
"ORDER BY balance DESC";
// 文本块写法
String sqlBlock = """
SELECT id, username, age, status, balance
FROM user
WHERE status = 1
ORDER BY balance DESC
""";九、综合实战:一个完整的用户 CRUD 服务
以下示例综合运用了本文所有知识点,展示一个内存版用户服务的完整实现,并标注每段代码对应的 Lambda/函数式技术点。
public class UserService {
private final List<User> userStore = new CopyOnWriteArrayList<>();
private final AtomicLong idGenerator = new AtomicLong(1);
// ==================== 增(Create) ====================
/** 新增单个用户。Supplier 用于生成默认值。 */
public User create(String username, Integer age) {
Supplier<User> factory = () -> {
User u = new User();
u.setId(idGenerator.getAndIncrement());
u.setUsername(username);
u.setAge(age);
u.setStatus(1);
u.setBalance(BigDecimal.ZERO);
return u;
};
User user = factory.get();
userStore.add(user);
return user;
}
/** 批量新增。Function 用于字段映射。 */
public List<User> batchCreate(List<String> names) {
List<User> created = names.stream()
.map(name -> create(name, 18)) // Function<String, User>
.collect(Collectors.toList());
return created;
}
// ==================== 删(Delete) ====================
/** 按 ID 删除。removeIf 配合 Predicate。 */
public boolean deleteById(Long id) {
return userStore.removeIf(u -> u.getId().equals(id));
}
/** 按条件批量删除。Predicate 组合。 */
public int deleteByCondition(Predicate<User> condition) {
int before = userStore.size();
userStore.removeIf(condition);
return before - userStore.size();
}
/** 删除所有冻结用户 */
public int deleteAllFrozen() {
return deleteByCondition(u -> u.getStatus() == 2);
}
// ==================== 改(Update) ====================
/** 更新用户信息。BiConsumer 接收 id 和更新操作。 */
public void update(Long id, Consumer<User> updater) {
userStore.stream()
.filter(u -> u.getId().equals(id))
.findFirst()
.ifPresent(updater); // Consumer 执行副作用
}
/** 批量将所有用户余额增加指定值。UnaryOperator 原地替换。 */
public void increaseAllBalance(BigDecimal amount) {
userStore.replaceAll(user -> {
user.setBalance(user.getBalance().add(amount));
return user;
});
}
// ==================== 查(Read) ====================
/** 按 ID 查询。Optional + Predicate。 */
public Optional<User> findById(Long id) {
return userStore.stream()
.filter(u -> u.getId().equals(id))
.findFirst();
}
/** 条件查询 + 排序 + 分页。完整 Stream 管道。 */
public List<User> query(UserQuery query) {
return userStore.stream()
.filter(u -> query.name() == null
|| u.getUsername().contains(query.name()))
.filter(u -> query.status() == null
|| u.getStatus().equals(query.status()))
.filter(u -> query.minBalance() == null
|| u.getBalance().compareTo(query.minBalance()) >= 0)
.sorted(Comparator.comparing(User::getAge)
.thenComparing(User::getUsername))
.skip((long) query.page() * query.size())
.limit(query.size())
.collect(Collectors.toList());
}
/** 统计每个状态的用户数量。groupingBy + counting。 */
public Map<Integer, Long> countByStatus() {
return userStore.stream()
.collect(Collectors.groupingBy(
User::getStatus,
Collectors.counting()
));
}
/** 查找余额最高的 N 个用户。sorted + limit。 */
public List<User> findTopNByBalance(int n) {
return userStore.stream()
.sorted(Comparator.comparing(User::getBalance).reversed())
.limit(n)
.collect(Collectors.toList());
}
/** 判断是否存在指定用户名的用户。anyMatch。 */
public boolean existsByUsername(String username) {
return userStore.stream()
.anyMatch(u -> u.getUsername().equals(username));
}
}
// 配套的 Record 查询参数(JDK 16+)
public record UserQuery(String name, Integer status,
BigDecimal minBalance, int page, int size) {
// 紧凑构造器,提供默认值
public UserQuery {
if (page < 0) page = 0;
if (size <= 0) size = 10;
}
}十、常见陷阱与最佳实践
陷阱 1:在 Lambda 中修改外部局部变量
Lambda 只能捕获 effectively final 的局部变量,不能修改。
// 编译错误:counter 不是 effectively final
int counter = 0;
users.forEach(u -> counter++); // ❌
// 正确做法:用数组或 AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
users.forEach(u -> counter.incrementAndGet()); // ✅陷阱 2:Stream 的惰性求值
中间操作不会被立即执行,只有终端操作触发时才执行整条管道。
// 没有终端操作,什么都不会发生
users.stream().filter(u -> {
System.out.println("筛选: " + u.getUsername()); // 不会打印
return u.getStatus() == 1;
});
// 加上终端操作才会执行
users.stream()
.filter(u -> u.getStatus() == 1)
.collect(Collectors.toList()); // 此时才会遍历陷阱 3:在 forEach 中修改集合
// ConcurrentModificationException
users.forEach(u -> {
if (u.getStatus() == 2) {
users.remove(u); // ❌ 在遍历中修改集合
}
});
// 正确:用 removeIf
users.removeIf(u -> u.getStatus() == 2); // ✅最佳实践清单
- 优先使用方法引用:当 Lambda 体只是调用已有方法时,User::getUsername 比 u -> u.getUsername() 更清晰。
- 方法引用中避免有副作用的 forEach:如果目的是转换数据,用 map + collect;只有在真正需要副作用(如打印、日志)时才用 forEach。
- 动态条件用 condition 参数:MyBatis-Plus 的条件方法第一个参数是 boolean,这是构建动态 SQL 的惯用法。
- Record 优先用于只读数据:查询参数、API 响应、值对象用 Record,实体类(需要可变状态)仍用普通类。
- JDK 17 中优先使用 switch 表达式:替代传统的 switch 语句和冗长的 if-else 链。
- 注意 Stream 的可读性:当管道超过 5-6 个操作时,考虑拆分为多个步骤或提取为命名方法。
以上内容覆盖了 Lambda 表达式在 CRUD 业务中的完整知识体系:从四大函数式接口的契约与组合,到 Collection 和 Stream 的增删改查操作,再到 MyBatis-Plus 的 LambdaQueryWrapper 实战,以及 JDK 11(var 参数、String 增强、HTTP Client)和 JDK 17(Record、密封类、Switch 表达式、文本块)的专属增强。掌握这些内容后,你应当能够在实际项目中写出类型安全、表达清晰、易于维护的 CRUD 代码。