Java类单继承但可多实现接口,接口仅声明能力、不存状态;组合优于继承,通过接口引用聚合功能组件;default方法限于无状态辅助逻辑,避免破坏接口语义。

Java里不能用extends实现多继承,但接口可以多实现
Java类只能单继承,这是语言层面的硬限制。但接口(interface)天生支持多实现——一个类可以同时implements多个接口,从而获得多个契约定义。这不是“模拟”多继承,而是Java官方认可的替代路径。
常见错误是把接口当抽象类用:在接口里塞private方法、写带逻辑的字段、甚至试图调用this——这些都会编译报错。接口只负责声明能力,不承载状态或具体行为。
- 接口中所有方法默认是
public abstract,无需显式写 - 从Java 8开始可定义
default方法,但它们不能访问实例字段,仅用于提供通用逻辑骨架 - 接口中字段自动是
public static final,本质就是常量,别指望它存对象状态
组合优于继承:用成员变量+接口引用代替多父类
当需要复用多个类的行为(比如既要能save()又要能encrypt()),直接让类继承两个父类不可能;更稳妥的做法是把功能拆成独立组件,再通过接口聚合。
典型场景:一个UserService既要记录日志,又要发通知。与其搞出ServiceWithLogAndNotify extends BaseService这种脆弱继承链,不如让UserService持有Logger和Notifier两个接口引用。
立即学习“Java免费学习笔记(深入)”;
- 每个组件实现对应接口(如
Logger、Notifier),彼此解耦 -
UserService通过构造函数或setXXX()注入依赖,而不是继承 - 调用时走接口方法(
logger.log("xxx")),不关心底层是FileLogger还是SlackNotifier
这样改起来快,测起来轻,换实现不伤主逻辑——而继承一旦写死,修改成本高得离谱。
default方法不是万能补丁,滥用会破坏接口语义
有人看到default方法就以为能“在接口里写实现”,结果把本该由具体类决定的行为全塞进接口,导致所有实现类被动继承一套固定逻辑,反而丧失灵活性。
典型踩坑:在PaymentProcessor接口里加default void refund() { /* 调用第三方API */ }——这会让CashPayment也带上网络请求逻辑,明显违背单一职责。
-
default方法适合提供无状态、通用、非核心的辅助行为(比如toString()格式化、集合工具方法) - 涉及IO、状态变更、业务规则判断的,必须留给实现类自己写
- 如果发现多个实现类重复写同一段逻辑,优先考虑提取为工具类(
PaymentUtils.refund(...)),而不是塞进接口
组合模式下如何避免循环依赖和空指针
组合看似简单,但实际编码时最容易栽在初始化顺序和空引用上。比如UserRepository依赖DatabaseConnection,而DatabaseConnection又需要UserRepository做连接池健康检查——这就形成循环依赖。
另一个高频问题是:成员变量声明了,但忘了注入,一调logger.info()就抛NullPointerException。
- 用构造函数强制注入关键依赖(
public UserService(Logger logger, Notifier notifier)),避免null风险 - 对可选依赖用
Optional<Notifier>包装,或提供带默认实现的重载构造器 - 避免在构造函数里调用被注入对象的方法(防止子类重写后引发未初始化问题)
- Spring等框架里,循环依赖可通过
@Lazy或setter注入缓解,但最好从设计上拆解职责
组合不是把一堆对象new出来就行,关键是理清谁创建谁、谁持有谁、谁先初始化——这点比语法细节更影响系统稳定性。










