优先选接口定义“能做什么”的契约,如Serializable;选抽象类提供“怎么做”的模板,如AbstractList封装通用逻辑。现代Java常协同使用:接口定义能力,抽象类提供默认实现。

选接口还是抽象类,关键看你要解决什么问题:如果重点是定义“能做什么”,用接口;如果重点是提供“怎么做”的公共实现或强制子类共享某种结构,用抽象类。
核心区别:契约 vs 模板
接口本质是一份能力契约——只声明方法签名,不关心实现,允许多实现,适合描述行为规范(比如Serializable、Comparable)。抽象类更像一个半成品模板——可含具体方法、字段、构造器,子类必须继承且只能单继承,适合构建有共性逻辑的类族(比如AbstractList封装了size()、isEmpty()等通用逻辑)。
什么时候优先用接口
- 需要让不相关的类具备同一行为(如Runnable让线程、定时任务、回调都能执行run())
- 要支持多重能力组合(一个类可同时实现Cloneable + Serializable + Comparable)
- 设计对外公开的稳定契约(接口一旦发布,修改成本高,所以Java 8后用default和static方法谨慎扩展)
- 面向未来扩展(比如新增Stream操作,直接在Collection接口加default stream(),无需改所有实现类)
什么时候优先用抽象类
- 多个子类之间存在大量重复代码(如数据库连接初始化、日志记录、资源清理等模板逻辑)
- 需要控制子类的构造过程(抽象类可定义受保护的构造器,限制实例化方式)
- 要为子类提供共享状态(比如抽象类中定义protected final Logger logger,所有子类复用)
- 已有类层次较深,需插入一层共性抽象(如从Animal抽象出Mammal,统一处理哺乳行为)
现代Java中的协同使用策略
不必非此即彼。常见模式是“接口定义能力,抽象类提供默认实现”。例如:
- List是接口,定义增删查改行为;AbstractList是抽象类,实现了indexOf()、lastIndexOf()等基于iterator()的通用逻辑;ArrayList和LinkedList各自继承AbstractList,专注优化底层存储
- 自定义框架中,先定义Processor接口声明process(),再提供BaseProcessor抽象类预置参数校验、监控埋点等横切逻辑










