《Effective Java》解读第44条:坚持使用标准的函数接口

第44条:坚持使用标准的函数接口

只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门再构建一个新的函数接口。

函数接口

Java 8 引入 java.util.function 包,里面定义了大量函数式接口,比如:

接口 函数签名 范例
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T,R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println

以及一些其他的变体,所有变体接口的命名都遵循「功能前缀 + 类型标识 + 基础接口名」的组合逻辑。

组成部分 含义 & 常见取值 示例
基础接口名 保留核心功能:Function/Predicate/Consumer/Supplier/Operator Function(函数)、Consumer(消费)
类型标识 标注处理的基本类型:Int/Long/Double(仅这三种,覆盖绝大多数场景) Int(处理 int)、Double(处理 double)
功能前缀 标注「方向 / 参数数量」: 1. ToXXX:表示 "返回 XXX 类型" 2. ObjXXX:表示 "泛型 + XXX 类型" 3. Bi:表示 "双参数" ToInt(返回 int)、ObjLong(泛型 + long)、Bi(双参数)

为什么要坚持使用?

  1. 减少学习成本
    如果你写一个方法,参数类型是自定义的 MyTransformer<T, R>,其他开发者看到后必须先理解这个接口的用途。
    但如果直接用 Function<T, R>,任何人一看就知道是"输入 T,输出 R",无需额外文档。
  2. 提高互操作性与组合能力
java 复制代码
// 不推荐
public interface StringProcessor {
    String process(String s);
}

// 推荐
public void handle(Function<String, String> processor) { ... }

// 使用
handle(String::toUpperCase);
  1. 避免接口爆炸
    如果一个库或应用为每种转型、每种参数数量都自定义函数接口,会导致大量几乎重复的接口,增加认知负担。

什么时候不使用?

  1. 需要多个参数

    比如 (T, U, V) -> R,而标准接口只有 Function(一个参数)和 BiFunction(两个参数)。超过两个参数时,要么自定义,要么用更不直观的方式(如用元组或类包装)。

  2. 需要受检异常

    标准函数接口的方法(如 apply)都不允许抛出受检异常。如果你的操作可能抛出 IOException 等,自定义接口可以声明 throws Exception,更方便处理。

  3. 语义更明确

    例如 java.util.Comparator 就是一个很好的自定义函数接口。虽然功能上它可以用 BiFunction<T, T, Integer> 代替,但 Comparator 表达了"比较器"这一明确语义,并提供了 thenComparing、reversed 等丰富方法。

  4. 需要为接口添加额外的方法

    标准接口只有单个抽象方法,但如果你需要多个默认方法或静态方法来增强用途(如 Comparator 那样),自定义接口更合适。

总结

  1. 优先使用 java.util.function 中的标准函数接口,能让 API 更易读、更通用、更方便与其他 Java 特性协作。

  2. 只在有明确理由(参数数量 > 2、需要受检异常、需要更丰富的 API 或更强的语义)时才自定义函数式接口。

  3. 自定义时,接口命名要体现其用途(如 Comparator、ToLongFunction 等),并标注 @FunctionalInterface。

相关推荐
mldong2 小时前
引擎从不发一条消息:jeeflow 的两类扩展点与消息模块的分工
java·架构
君顾13 小时前
智慧零售实战指南:从技术架构到落地方案全解析
java·开发语言·零售
南棱笑笑生3 小时前
20260904实测给飞凌OK3576-C开发板刷入Rockchip原厂的IMG固件【使用飞凌的DTS】切换串口波特率为1.5Mbps
c语言·开发语言·rockchip
zhanghe6873 小时前
es的密码带有特殊字符,导出
java
平头哥技术团队4 小时前
Day 10 | 工欲善其事:VS Code 配置与项目归档
android·开发语言·前端·javascript·html·交互
pnoker4 小时前
IoT DC3 概念解读:把设备抽象成一套语义模板——位号、指令、事件与面向智能体的设备建模
java·人工智能·物联网·iot·dc3
乌萨奇也要立志学C++4 小时前
【洛谷】kmp算法
开发语言·算法
my_realmy4 小时前
Java SE 基础学习笔记(一):面向对象、继承多态、抽象接口与异常(JDK 8)
java·多线程·并发··生产者消费者模式
传奇开心果编程4 小时前
【Rust入门知识点学与练】第4课:条件判断 if / else
开发语言·学习·rust
m0_380743874 小时前
给 Claude API 调用补上超时重试和错误分类
开发语言·人工智能·php