Java 函数式接口实战笔记
用 Lambda 表达式写了两三年,但真正把函数式接口理清楚是在一次代码 Review 上。同事指出我用 if-else 写的一段多策略逻辑可以用 Map<String, Function> 完全替代。回头一看,才发现自己对 Function、Consumer、Supplier、Predicate 的区别一直停留在”会用”,没真正整理过。这篇做个梳理,重点放在业务场景里真实用到的地方。
一、四大接口速查
Java 8 的 java.util.function 包里预置了几十个函数式接口,但日常开发 90% 的场景都落在这四个上:
| 接口 | 方法签名 | 记忆口诀 | 典型场景 |
|---|
Function<T, R> | R apply(T t) | 有入有出,做转换 | 对象属性提取、类型转换、格式化 |
Consumer<T> | void accept(T t) | 有入无出,做消费 | 遍历处理、打印日志、调用外部接口 |
Supplier<T> | T get() | 无入有出,做生产 | 懒加载、默认值兜底、工厂方法 |
Predicate<T> | boolean test(T t) | 有入布尔出,做判断 | 过滤、校验、条件组合 |
二、Function — 转换
Function<T, R> 接收 T 类型入参,返回 R 类型结果,核心是数据转换。
Function<Long, OrderDetail> queryOrder = id -> orderService.getById(id); OrderDetail detail = queryOrder.apply(1001L);
Function<Long, OrderDetail> queryOrder = orderService::getById;
|
组合:andThen / compose
Function<String, String> trim = String::trim; Function<String, String> toUpper = String::toUpperCase;
Function<String, String> trimThenUpper = trim.andThen(toUpper); trimThenUpper.apply(" hello ");
Function<String, String> upperThenTrim = trim.compose(toUpper);
|
业务场景:Map 策略替代 if-else
多种数据源类型的处理逻辑,之前用大量 if-else,改成 Map<String, Function> 后一目了然:
Map<String, Function<ExportParam, List<?>>> exportHandlers = new HashMap<>(); exportHandlers.put("order", param -> orderService.listForExport(param)); exportHandlers.put("product", param -> productService.listForExport(param)); exportHandlers.put("user", param -> userService.listForExport(param));
Function<ExportParam, List<?>> handler = exportHandlers.get(type); if (handler == null) throw new BizException("不支持的导出类型:" + type); List<?> data = handler.apply(param);
|
三、Consumer — 消费
Consumer<T> 只有入参,没有返回值,核心是执行副作用(打日志、写库、调接口等)。
Consumer<String> print = System.out::println; print.accept("hello");
List<Order> orders = ...; orders.forEach(order -> orderService.process(order));
orders.forEach(orderService::process);
|
组合:andThen
多个消费动作串联,全部执行:
Consumer<Order> sendNotify = order -> notifyService.send(order); Consumer<Order> saveOpLog = order -> opLogService.save(order, "已处理"); Consumer<Order> updateStatus = order -> orderService.updateStatus(order.getId(), DONE);
Consumer<Order> allInOne = updateStatus.andThen(sendNotify).andThen(saveOpLog); orders.forEach(allInOne);
|
业务场景:Builder 模式中的 Consumer 参数
给实体对象赋值时,可以把额外的配置操作通过 Consumer 传入,避免写太多重载方法:
public Order buildOrder(Long userId, Consumer<Order> customizer) { Order order = new Order(); order.setUserId(userId); order.setCreateTime(LocalDateTime.now()); order.setStatus(OrderStatus.CREATED); customizer.accept(order); return order; }
Order o = buildOrder(userId, order -> { order.setRemark("来自活动"); order.setSource("H5"); });
|
四、Supplier — 生产
Supplier<T> 没有入参,只有返回值,核心是延迟求值——只有真正需要时才执行。
Supplier<LocalDateTime> now = LocalDateTime::now; System.out.println(now.get());
|
业务场景:默认值兜底
Optional.orElseGet() 用的就是 Supplier,比 orElse() 更懒:
User user = Optional.ofNullable(cache.get(id)).orElse(userService.queryFromDb(id));
User user = Optional.ofNullable(cache.get(id)).orElseGet(() -> userService.queryFromDb(id));
|
业务场景:懒加载配置
private Supplier<SystemConfig> configSupplier = () -> configService.loadGlobal();
public void doSomething() { if (needConfig) { SystemConfig config = configSupplier.get(); } }
|
五、Predicate — 判断
Predicate<T> 接收一个入参,返回 boolean,核心是条件判断与过滤。
Predicate<String> notBlank = s -> s != null && !s.isBlank(); notBlank.test("hello"); notBlank.test("");
|
组合:and / or / negate
逻辑组合是 Predicate 最实用的地方:
Predicate<Order> isPaid = order -> order.getStatus() == PAID; Predicate<Order> isTimeout = order -> order.getCreateTime().isBefore(LocalDateTime.now().minusHours(24)); Predicate<Order> isVip = order -> userService.isVip(order.getUserId());
Predicate<Order> validOrder = isPaid.and(isTimeout.negate());
Predicate<Order> needPriority = isTimeout.or(isVip);
List<Order> validOrders = orders.stream() .filter(validOrder) .collect(Collectors.toList());
|
业务场景:参数化校验链
校验规则可以作为 Predicate 列表传入,统一执行并收集错误:
public <T> List<String> validate(T target, List<Map.Entry<Predicate<T>, String>> rules) { List<String> errors = new ArrayList<>(); for (Map.Entry<Predicate<T>, String> rule : rules) { if (!rule.getKey().test(target)) { errors.add(rule.getValue()); } } return errors; }
List<Map.Entry<Predicate<OrderParam>, String>> rules = List.of( Map.entry(p -> p.getUserId() != null, "用户ID不能为空"), Map.entry(p -> p.getAmount() != null, "金额不能为空"), Map.entry(p -> p.getAmount().compareTo(BigDecimal.ZERO) > 0, "金额必须大于0") ); List<String> errors = validate(param, rules);
|
六、方法引用速查
Lambda 能写得更简洁的地方,优先用方法引用:
| 形式 | 写法 | 等价 Lambda |
|---|
| 静态方法引用 | Integer::parseInt | s -> Integer.parseInt(s) |
| 实例方法引用(绑定) | "hello"::toUpperCase | () -> "hello".toUpperCase() |
| 实例方法引用(非绑定) | String::toUpperCase | s -> s.toUpperCase() |
| 构造器引用 | Order::new | () -> new Order() |
七、小结
四个接口记忆的核心就是入参和出参:
- 有入有出 →
Function(转换) - 有入无出 →
Consumer(消费) - 无入有出 →
Supplier(生产) - 有入布尔 →
Predicate(判断)
业务代码里最实用的几个场景:用 Map<String, Function> 替代 if-else 策略分发、用 Predicate 组合替代多条件判断、用 orElseGet(Supplier) 代替 orElse 做懒加载。把这些用熟之后,代码的可读性和可测试性都会有明显提升。