Java 函数式接口全解——Predicate、Function、Consumer 的组合艺术
Java 函数式接口全解——Predicate、Function、Consumer 的组合艺术
适读人群:了解 Lambda 基础用法但还没用好函数式接口的 Java 工程师 | 阅读时长:约13分钟 | 核心价值:函数式接口的组合能力,以及如何用它写出更灵活的设计
Java 8 带来了 Lambda 表达式,但我发现很多人用了好几年,也只是把它当"更短的匿名内部类"在用。Lambda 的真正价值,是背后那套函数式接口和函数组合的能力。
这篇文章我不讲 Lambda 语法,只讲函数式接口的组合用法,这部分在实际项目里能解决一些很实际的设计问题。
一、四个核心函数式接口
java.util.function 包里有很多接口,但核心的就四个:
| 接口 | 方法签名 | 含义 |
|---|---|---|
Predicate<T> | boolean test(T t) | 判断:给一个值,返回 true/false |
Function<T, R> | R apply(T t) | 转换:给一个值,返回另一个类型的值 |
Consumer<T> | void accept(T t) | 消费:给一个值,执行某个动作,无返回值 |
Supplier<T> | T get() | 供给:不接收参数,返回一个值 |
其他的(BiFunction、UnaryOperator、IntPredicate 等)都是这四个的变体或特化版。
二、Predicate:组合判断条件
Predicate 有三个默认方法:and()、or()、negate(),可以组合条件:
Predicate<User> isActive = user -> user.isActive();
Predicate<User> isAdmin = user -> user.getRoles().contains("ADMIN");
Predicate<User> hasEmail = user -> user.getEmail() != null && !user.getEmail().isEmpty();
// 组合:活跃的管理员
Predicate<User> activeAdmin = isActive.and(isAdmin);
// 组合:活跃的或有邮箱的
Predicate<User> activeOrHasEmail = isActive.or(hasEmail);
// 取反:不活跃的
Predicate<User> inactive = isActive.negate();
// 在 Stream 里用
List<User> activeAdmins = users.stream()
.filter(isActive.and(isAdmin))
.collect(Collectors.toList());这种组合方式在规则复杂的业务场景里非常有用:
// 优惠券适用条件
Predicate<Order> isEligible =
((Predicate<Order>) order -> order.getTotalAmount().compareTo(new BigDecimal("100")) >= 0)
.and(order -> order.getUserLevel() >= 2)
.and(order -> !order.isAlreadyUsedCoupon())
.and(order -> order.getProductCategory().equals("ELECTRONICS"));
// 使用
if (isEligible.test(order)) {
applyCoupon(order);
}把条件分开定义,组合起来,比一个大 if 语句可读性好很多,而且条件可以复用。
三、Function:转换链
Function 有 andThen() 和 compose() 方法:
Function<String, String> trim = String::trim;
Function<String, String> toLowerCase = String::toLowerCase;
Function<String, Integer> length = String::length;
// andThen:先执行 this,再执行 after
Function<String, String> cleanInput = trim.andThen(toLowerCase);
// compose:先执行 before,再执行 this(顺序和 andThen 相反)
Function<String, String> lowerThenTrim = trim.compose(toLowerCase);
// 链式:先 trim,再 toLowerCase,再取长度
Function<String, Integer> pipeline = trim.andThen(toLowerCase).andThen(length);
System.out.println(pipeline.apply(" Hello World ")); // 11实际应用:数据清洗管道
// 数据清洗:多个步骤,每步可以独立测试和替换
Function<String, String> normalizePhone = phone -> phone.replaceAll("[\\s\\-\\(\\)]", "");
Function<String, String> addCountryCode = phone -> phone.startsWith("0") ? "+86" + phone.substring(1) : phone;
Function<String, String> validatePhone = phone -> {
if (!phone.matches("\\+86\\d{11}")) {
throw new IllegalArgumentException("无效的手机号: " + phone);
}
return phone;
};
Function<String, String> phoneProcessor = normalizePhone
.andThen(addCountryCode)
.andThen(validatePhone);
// 使用
String cleanPhone = phoneProcessor.apply(" 138-0013-8000 ");四、Consumer:副作用操作的链式处理
Consumer 有 andThen() 方法,可以链式执行多个副作用操作:
Consumer<User> logUserLogin = user ->
log.info("用户登录: {}, time={}", user.getUsername(), LocalDateTime.now());
Consumer<User> updateLastLoginTime = user ->
userRepository.updateLastLoginAt(user.getId(), LocalDateTime.now());
Consumer<User> sendWelcomeEmail = user -> {
if (user.isFirstLogin()) {
emailService.sendWelcome(user.getEmail());
}
};
// 组合:登录时的一系列副作用
Consumer<User> onUserLogin = logUserLogin
.andThen(updateLastLoginTime)
.andThen(sendWelcomeEmail);
// 使用
onUserLogin.accept(loggedInUser);五、把函数式接口作为方法参数:策略模式的简化版
函数式接口作为参数传递,是实现策略模式最简洁的方式:
// 传统策略模式:需要定义接口、多个实现类,代码量大
interface SortStrategy {
List<Product> sort(List<Product> products);
}
class PriceSortStrategy implements SortStrategy { ... }
class RatingSortStrategy implements SortStrategy { ... }
// 用函数式接口:直接传 Lambda 或方法引用
public List<Product> getProducts(
Predicate<Product> filter,
Comparator<Product> sorter,
Function<Product, ProductVO> converter) {
return productRepository.findAll().stream()
.filter(filter)
.sorted(sorter)
.map(converter)
.collect(Collectors.toList());
}
// 调用方可以灵活组合
List<ProductVO> result = productService.getProducts(
p -> p.isOnSale() && p.getStock() > 0, // 过滤:在售且有库存
Comparator.comparing(Product::getPrice), // 按价格升序
ProductVO::from // 转换
);这个模式在 Repository 层的动态查询场景里特别好用:
// 用 Predicate 动态组合查询条件(内存查询,不是数据库查询)
public List<Order> findOrders(
Long userId,
OrderStatus status,
LocalDate startDate,
LocalDate endDate) {
Predicate<Order> condition = order -> true; // 初始条件:全选
if (userId != null) {
condition = condition.and(order -> order.getUserId().equals(userId));
}
if (status != null) {
condition = condition.and(order -> order.getStatus() == status);
}
if (startDate != null) {
condition = condition.and(order -> !order.getCreatedAt().toLocalDate().isBefore(startDate));
}
if (endDate != null) {
condition = condition.and(order -> !order.getCreatedAt().toLocalDate().isAfter(endDate));
}
return orderRepository.findAll().stream()
.filter(condition)
.collect(Collectors.toList());
}六、BiFunction 和 BinaryOperator:两个参数的情况
// BiFunction<T, U, R>:两个入参,一个返回值
BiFunction<User, List<Order>, UserOrderSummary> buildSummary =
(user, orders) -> new UserOrderSummary(
user.getUsername(),
orders.size(),
orders.stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add)
);
// BinaryOperator<T>:两个同类型入参,返回同类型(继承自 BiFunction)
BinaryOperator<BigDecimal> sum = BigDecimal::add;
BigDecimal total = amounts.stream().reduce(BigDecimal.ZERO, sum);七、自定义函数式接口
有时候内置的接口不够用,可以自定义:
// 带检查异常的 Function(内置的 Function 不能抛检查异常)
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
// 包装成不抛检查异常的版本
static <T, R> Function<T, R> wrap(CheckedFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
// 使用
List<User> users = userIds.stream()
.map(CheckedFunction.wrap(id -> userRepository.findByIdOrThrow(id))) // 方法会抛 IOException
.collect(Collectors.toList());函数式接口的组合能力,让你可以把"行为"像数据一样组合、传递、复用。这是一种和面向对象完全不同的设计视角,不是说哪种更好,而是多了一种工具。
很多时候,一个复杂的 if-else 判断逻辑,用 Predicate 的 and/or/negate 组合,代码的结构会清晰很多。
下一篇写 Java 模块系统 JPMS,这个特性估计是 Java 9 以来用得最少的之一,我说说我对它的真实看法。
