接口定义“能干什么”,用于能力契约与多角色组合;抽象类定义“是什么”,用于身份继承与流程复用。用错会导致设计僵化或无法共享状态。

面试官问“接口和抽象类的区别”,不是想听你背八股,而是想确认你有没有设计意识——能不能在真实场景里选对抽象工具。下面从三个硬核维度拆解,不列条目、不堆术语,只讲清楚“为什么这么设计”和“用错会怎样”。
一、先看本质:它们解决的是两类完全不同的问题
抽象类回答的是“它是什么”。比如Animal抽象类,狗、猫、鸟都继承它,因为它们本质上都是动物,共享name、age字段,都有eat()和sleep()这种基础行为。父类能提供默认实现,也能留抽象方法让子类填空。
接口回答的是“它能干什么”。比如Flyable、Swimmable、Payable,这些能力跟“是什么”无关——飞机不是鸟,但能飞;银行卡不是人,但能支付。一个类可以同时具备多种能力,所以允许实现多个接口。
记一个判断口诀:能用“是”字描述的,优先考虑抽象类;能用“能”字描述的,优先考虑接口。比如“微信支付是支付渠道”→适合抽象类;“微信支付能退款、能查单、能通知”→这些动作就该拆成接口。
二、再看语法:差异背后全是设计约束
抽象类可以有构造方法、protected字段、普通方法、静态方法、甚至带逻辑的初始化块。它像一份可运行的半成品模板,子类通过super()调用父类构造器,复用状态和流程。
接口不能有构造器,所有字段自动是public static final,所有非default方法自动是public abstract。JDK 8之后加了default和static方法,但目的很明确:default是为向后兼容加的“可选实现”,static只是工具方法,都不能改变接口“只定义契约”的定位。
关键细节:抽象类支持protected方法和字段,接口不行;抽象类能保存实例状态(比如缓存、配置对象),接口完全不能。如果你需要子类共享一个HttpClient实例或读取同一份配置,只能靠抽象类。
三、最后看实战:什么时候必须用哪个
必须用接口的场景:
- 对外暴露服务边界,比如UserService、OrderRepository——调用方只关心“能查用户”“能创建订单”,不关心你用MySQL还是MongoDB
- 需要多角色组合,比如一个消息处理器既要能解析(Parseable)、又要能路由(Routable)、还要能重试(Retryable)
- 做Mock或替换实现,比如测试时用MockPaymentService实现Payable接口,完全绕过真实签名逻辑
- 跨团队协作,对方只按接口写代码,你内部怎么重构都不影响契约
必须用抽象类的场景:
- 固定流程+可变步骤,比如退款服务:校验→生成单号→调第三方→记录日志,只有“调第三方”这一步每家渠道都不同,用模板方法模式最合适
- 子类需要共享初始化逻辑,比如所有数据库操作类都要加载连接池、设置超时时间,抽象基类里统一做
- 需要定义protected钩子方法,供子类选择性覆盖,又不想暴露给外部调用者
- 已有类层级较深,新增共性功能不方便改接口(比如老系统里所有DAO都继承BaseDao,现在要加统一审计日志)
真正卡住人的,从来不是语法差异,而是没想清楚:你是在定义身份,还是在定义能力?是在复用代码,还是在约定行为?把这个问题想透,答案自然就出来了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











