接口定义行为契约,只声明“能做什么”,命名须为动词性短语(如Payable),职责单一、放api包;抽象类提供共性骨架,命名以Abstract/Base开头,封装通用流程,放domain包;选型依“是XX”用抽象类、“能XX”用接口、“既要又要”则组合使用。

接口与抽象类在团队开发中不是语法选择题,而是架构分工的契约声明。用错一个,轻则子类改得浑身难受,重则模块间耦合成网、演进举步维艰。关键不在“能不能用”,而在“谁该承担什么职责”。
接口:只管“能做什么”,不问“怎么做”
它定义的是行为契约,不是实现模板。团队必须统一认知:接口名必须是动词性短语或能力描述(如 Payable、Exportable、Retryable),不能叫 BasePayment 或 AbstractExporter——那是抽象类的地盘。
- 所有方法默认 public abstract,禁止显式加 private/protected/final/static;JDK8+ 的 default 方法仅用于向后兼容,不得承载核心业务逻辑
- 常量必须是真正全局不变的配置项(如 TIMEOUT_SECONDS = 30),禁止放可变状态或业务参数
- 一个类可 implements 多个接口,但每个接口职责必须单一——比如 Validatable 和 Serializable 分开,不合并为 ValidatableAndSerializable
- 接口应放在 api 或 contract 包下,与实现隔离;对外暴露的接口需加 @since 和 @deprecated 注释,演进受版本控制
抽象类:专注“共性怎么搭”,留好“个性怎么填”
它是子类的骨架提供者,不是行为说明书。团队命名必须强制:以 Abstract 或 Base 开头(如 AbstractOrderService、BaseEntity),且文件名与类名严格一致。重复前缀(如 AbstractBaseUser)属于编译级违规。
- 抽象方法禁止用 private/static/final 修饰;普通方法只封装通用流程(如统一日志埋点、空值校验),不写 if-else 分支业务判断
- 字段建议 protected + 提供 protected getter/setter,杜绝 public 字段;构造方法可用 protected,用于强制子类初始化必要上下文
- 继承链深度 ≤3 层(如 AbstractEntity → AbstractAuditableEntity → AbstractTenantEntity),超深链必须重构为组合
- 抽象类必须放在 domain 或 core 包,作为稳定内核;新增抽象方法属于不兼容变更,需同步更新所有非抽象子类并走 CR 流程
选型决策树:三句话定乾坤
团队晨会或 PR Review 时,可直接用这三条判断:
立即学习“Java免费学习笔记(深入)”;
- 要表达“这类东西本质上是一种XX”(如订单是一种可取消资源)→ 用 抽象类
- 要表达“这个东西应该能干XX事”(如支付方式应该能发起、能查询、能退款)→ 用 接口
- 一个类既要复用模板,又要实现多种行为 → 继承抽象类 + implements 多个接口(如 class AlipayService extends AbstractPaymentService implements Payable, Refundable, Notifyable)
高频雷区与团队禁令
以下行为在代码扫描和 CR 中一票否决:
- 接口里写 public static void main() —— 入口不属于契约层
- 抽象类中定义 public 字段或把 protected 方法改为 public —— 破坏封装边界
- 子类把父类已实现的方法重写为 abstract —— 这是契约倒退,不是扩展
- 用抽象类替代接口来定义纯行为规范(如把 Runnable 写成 AbstractRunnable)—— 违反设计思想


















