接口是行为契约,规定“能做什么”;抽象类是类的模板,定义“是什么”。接口支持多实现、无状态、强调能力扩展;抽象类支持单继承、可含状态与构造器、侧重代码复用和层次设计。

接口与抽象类不是非此即彼的选择,而是面向对象设计中互补的两种抽象工具——关键不在“用哪个”,而在“各自承担什么职责”。它们共同服务于开闭原则、里氏替换、依赖倒置等核心原则,但分工明确。
接口:落实“依赖倒置”和“面向契约”
接口本质是能力契约,不关心“是谁”,只规定“能做什么”。它让高层模块(如业务逻辑)不依赖具体实现,而依赖一组稳定的行为声明。
- 当多个不相关的类需要提供相同行为(比如
Payable、Loggable、Flyable),用接口;它们可能分属不同继承体系,无法共用父类 - 接口适合定义可插拔的能力点。例如支付模块不关心你是微信支付还是支付宝支付,只要它实现了
PaymentProcessor接口,就能被统一调用 - Java 8+ 的 default 方法不是为了替代抽象类,而是为已有接口安全扩展方法——避免修改所有实现类,守住“对扩展开放,对修改关闭”的开闭原则
抽象类:支撑“里氏替换”和“模板复用”
抽象类代表“一类事物”的共性骨架,强调“是什么”。它天然承载状态(字段)、共享逻辑(具体方法)、初始化流程(构造器),并强制子类遵循统一结构。
- 当一组类有共同属性(如
name、id)、共同校验逻辑(如validate())、共同生命周期行为(如init()、destroy()),抽象类比接口更合适 - 模板方法模式就依赖抽象类:定义算法骨架(具体方法),把可变步骤留作抽象方法由子类实现。这样既保证流程一致,又允许细节定制
- 子类继承抽象类后,可以当作父类型使用(向上转型),天然满足里氏替换原则——因为父类已约束了行为边界和状态基础
组合使用才是常态:接口定义能力,抽象类实现骨架
真实项目中,二者常协同工作。比如一个消息发送系统:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 先定义接口
MessageSender,声明send()、supportsType()等契约,供外部依赖 - 再提供抽象类
AbstractMessageSender,封装通用日志、重试、限流逻辑,并定义抽象方法doSend()留给子类实现 - 具体类如
SmsSender、EmailSender继承抽象类,同时隐式满足接口契约 - 上层代码只面向
MessageSender接口编程,运行时注入具体子类实例——既松耦合,又享复用
判断依据要回归设计意图,而非语法特征
别纠结“能不能加字段”或“能不能写方法体”,而要问三个问题:
- 这个抽象是否表达一种“类别归属”?(是 → 抽象类)
- 这个抽象是否描述一种“可叠加的能力”?(是 → 接口)
- 后续扩展时,是更可能增加新行为(接口 default 方法),还是更可能引入共享状态或构造逻辑(抽象类)?
不复杂但容易忽略:平衡点不在代码行数或继承深度,而在职责是否清晰、变化是否隔离、协作是否自然。

















