
Java接口里不该放实现代码
接口不是工具类,default 方法和 static 方法容易被滥用成“偷懒的实现入口”。一旦业务逻辑塞进接口,实现类就失去控制权,单元测试难写,分支逻辑难覆盖,后续想换实现反而被接口绑死。
常见错误现象:NullPointerException 在调用 default 方法时才暴露,但堆栈指向接口而非具体实现,排查绕路;多人协作时有人顺手在接口里加了个 static 工具方法,结果被其他模块强依赖,改名或拆包直接编译报错。
- 只在接口中声明
public abstract方法(哪怕 Java 8+ 默认可省略abstract关键字) - 工具逻辑一律收进
Utils类或领域服务类,别让接口承担“不该它干的活” - 如果真需要共享行为,优先考虑组合:让实现类持有策略对象,而不是靠接口兜底
接口命名必须带业务语义,不带 Impl 或 Service 后缀
叫 OrderService 不如叫 OrderPlacement;叫 UserDao 不如叫 UserRepository。后缀暴露实现细节,而接口要表达“做什么”,不是“怎么做的”。团队看到 PaymentProcessor 就知道这是支付动作的契约,看到 PaymentServiceImpl 就默认这是某次临时写的、可能随时被替掉的代码。
使用场景:新成员接手模块时,第一眼扫包结构,靠接口名快速建立业务认知。如果满屏都是 XXXManager、XXXHandler,没人能分清哪个是核心契约,哪个是过渡方案。
立即学习“Java免费学习笔记(深入)”;
- 用动名词或名词短语,聚焦领域动作或职责边界,例如
InventoryReservation、NotificationDispatcher - 避免
Impl、Proxy、Stub、Mock出现在接口名里——这些属于实现侧的标记,不该污染契约层 - 包路径也要配合:业务接口统一放在
com.example.order.contract这类显式标识契约的包下,别混在impl或service包里
接口方法参数禁止裸用 Map / JSON String / Object
用 Map<string object></string> 当参数,等于放弃编译期校验和 IDE 提示。调用方传错 key 名、少传字段、类型错配,只能等运行时报 ClassCastException 或空指针——而且错误位置往往在接口内部,不是调用点。
性能影响:每次调用都要做 map.get("xxx") + 强转,比直接访问 POJO 字段慢,还容易触发不必要的装箱/反射。
- 每个方法参数定义明确的 DTO 类,哪怕只有两个字段,也单独建
PlaceOrderRequest - DTO 类字段必须用具体类型(
LocalDateTime而非Object),不可含transient或复杂逻辑 - 如果真需动态字段(比如配置扩展),用专用结构体,例如
ExtensionFields,并在 Javadoc 里写清 key 约束和生命周期
接口变更必须兼容旧客户端,别动已有方法签名
加参数、改返回类型、删方法,都会导致下游编译失败或运行时 NoSuchMethodError。Spring Cloud 或 Dubbo 场景下更麻烦:提供方发版了,消费方没跟上,整个链路就卡住。
正确做法是“只增不改”:新增接口、新增方法、新增 DTO 字段(加 @Deprecated 标记旧方法,但保留其实现),给足灰度周期。
- 不要重载方法——
process(Order)和process(Order, String)看似灵活,实则让调用方无法判断该用哪个,尤其涉及泛型时编译器常推导错 - 返回值尽量用不可变容器,例如
List<order></order>而非ArrayList<order></order>,避免消费方误以为能强转或修改底层结构 - 所有接口方法必须有
@NotNull/@NonNull注解(配合 Lombok 或 JetBrains 注解),IDE 和静态检查工具才能提前发现空值风险
最麻烦的其实是版本号管理——接口没显式版本,但实际每次不兼容变更都相当于升了一版。别指望靠 Git Tag 或文档记录,得在包名或接口名里体现,比如 v2.OrderPlacement。这点很多人拖到线上出问题才想起补。










