Java 函数式接口实战笔记

Java 函数式接口实战笔记

用 Lambda 表达式写了两三年,但真正把函数式接口理清楚是在一次代码 Review 上。同事指出我用 if-else 写的一段多策略逻辑可以用 Map<String, Function> 完全替代。回头一看,才发现自己对 FunctionConsumerSupplierPredicate 的区别一直停留在”会用”,没真正整理过。这篇做个梳理,重点放在业务场景里真实用到的地方。


一、四大接口速查

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 类型结果,核心是数据转换

// 基础用法:把订单 ID 转成订单详情
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;

// andThen:先 trim,再 toUpperCase
Function<String, String> trimThenUpper = trim.andThen(toUpper);
trimThenUpper.apply(" hello "); // → "HELLO"

// compose:先 toUpperCase,再 trim(顺序相反)
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));

// 调用时直接 get + apply,不再需要 if-else
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()); // 调用 get() 才真正执行

业务场景:默认值兜底

Optional.orElseGet() 用的就是 Supplier,比 orElse() 更懒:

// orElse:不管有没有值,都会执行括号内的表达式(有性能浪费)
User user = Optional.ofNullable(cache.get(id)).orElse(userService.queryFromDb(id));

// orElseGet:只有值为空时才调用 Supplier,推荐
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"); // → true
notBlank.test(""); // → false

组合: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());

// 组合:超时 或 VIP 用户
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::parseInts -> Integer.parseInt(s)
实例方法引用(绑定)"hello"::toUpperCase() -> "hello".toUpperCase()
实例方法引用(非绑定)String::toUpperCases -> s.toUpperCase()
构造器引用Order::new() -> new Order()

七、小结

四个接口记忆的核心就是入参和出参

  • 有入有出 → Function(转换)
  • 有入无出 → Consumer(消费)
  • 无入有出 → Supplier(生产)
  • 有入布尔 → Predicate(判断)

业务代码里最实用的几个场景:用 Map<String, Function> 替代 if-else 策略分发、用 Predicate 组合替代多条件判断、用 orElseGet(Supplier) 代替 orElse 做懒加载。把这些用熟之后,代码的可读性和可测试性都会有明显提升。