选抽象类还是接口,关键看语义:有“is-a”关系且需共享状态和逻辑时用抽象类;表达“能做什么”、支持多角色组合或第三方集成时用接口;真实项目中常混合使用,接口定契约,抽象类封装共性实现。

选接口还是抽象类,关键不在语法多像,而在于你想表达什么关系——是“它属于哪一类”,还是“它能做什么”。业务架构设计讲究语义清晰、扩展灵活、耦合可控,选错抽象机制,后期改起来成本极高。
看继承关系:有明确的“is-a”层级就用抽象类
当多个类天然属于同一类事物,共享状态(字段)、通用逻辑(方法实现)和初始化流程时,抽象类是更自然的选择。它承载的是血缘和骨架。
- 比如订单系统里,“支付方式”是一个典型抽象类:PayMethod 包含 commonId、createTime、validate() 具体校验逻辑,同时定义 abstract doPay() 让子类(Alipay、WeChatPay、BankTransfer)各自实现;子类共用字段、构造器和基础校验,避免重复代码
- 如果强行用接口,就得在每个实现类里重写 createTime 赋值、重复写日志或风控逻辑,违背 DRY 原则
- 注意:Java 单继承限制意味着,一旦用了抽象类,子类就失去了继承其他父类的机会——所以只在“确实需要共享状态+行为”的核心主干上用
看能力组合:跨领域、多角色、松耦合就用接口
当不同类没有继承关系,但需统一暴露某种能力,或者一个类要同时具备多种独立职责时,接口是唯一合理选项。它表达的是契约,不是出身。
- 例如风控模块中,“可审计”Auditable、“可重试”Retryable、“可补偿”Compensable 都应是接口;订单服务、退款服务、库存服务互不相干,但都可实现 Auditable,接入统一审计中心,彼此零耦合
- 再如微服务间通信:DTO 对象只需实现 Serializable 和 Validatable,不关心它是不是 Order 或 User,也不影响它继承自己的业务基类
- 接口越小越好,遵循接口隔离原则——别搞一个“BigServiceInterface”包揽所有方法,拆成 Queryable、Updatable、Cancelable 更易测试和演进
看演进需求:未来可能被多继承或第三方扩展就优先接口
业务系统常需对接外部 SDK、中间件或遗留系统,它们的类结构你无法修改。这时接口是唯一桥梁。
- 比如你设计了一个消息处理器抽象类 MessageHandler,但某第三方 MQ 客户端要求你继承它的 BaseConsumer 类——你只能二选一,冲突不可避免
- 若当初定义的是 MessageProcessor 接口,你的业务类就能同时 extends BaseConsumer implements MessageProcessor,无缝集成
- 同理,对外提供的 SPI(服务提供接口),必须用 interface:JDBC 的 Driver、Spring 的 BeanPostProcessor、Dubbo 的 Filter,全是接口——这是框架可插拔的设计根基
混合使用才是常态:抽象类实现接口,分层解耦
真实项目里,两者不是非此即彼,而是协作关系。顶层用接口定义能力契约,中层用抽象类封装通用实现,底层具体类专注差异化逻辑。
- 示例:电商搜索服务定义 Searchable 接口(search、suggest、facet);内部提供 AbstractElasticSearcher 抽象类,封装连接池管理、超时重试、结果转换等共性;各业务线继承它,只写 query 构建和结果组装
- 这样既保证了对外契约稳定(接口不变,下游不感知),又支撑了内部快速复用(抽象类升级,所有子类自动受益)
- 警惕反模式:抽象类里堆满 default 方法、接口里塞一堆 static 工具方法——说明设计边界模糊,应回归“谁该管状态,谁该管行为”去重构
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











