基础不牢,地动山摇
大家好,这里是后端基础拾光集。
面试必考题:接口和抽象类有什么区别?我们一起来看看工作中具体怎么使用
一、生活化类比
把系统设计想成公司管理:
- 接口 = 对外《岗位说明书》/能力认证 :只规定"这个岗位要做什么、必须会什么",不关心你是谁、你的内部属性是什么。不写"工号、薪资、审批权限、具体怎么干"。谁满足这份岗位要求,谁就能上岗;一个人还可以同时满足多份岗位说明书(一个类可以实现多个接口)。
- 抽象类 = 内部《岗位族手册 + SOP + 员工档案模板》 :理解成半成品模板,它已经有一部分现成的属性、基础逻辑,不仅规定职责,还自带通用流程、共享字段、默认实现;子类像具体岗位,继承这个岗位族,补上差异部分。通常只能认一个主岗位族,你不能同时基于两份完全不同的半成品来打造同一个产品(一个类只能继承一个抽象类)。
一句话记忆:接口定能力规范;抽象类做半成品模板。
二、什么时候用接口?
当你在写"招聘要求"时,用接口:
- 只关心能力,不关心实现
比如Payable:只要"能支付"就行,微信、支付宝、银行卡各自怎么付,调用方不关心。 - 多个不相关类都要具备同一能力
鸟、飞机、超人没有共同父类,但都可以实现Flyable。 - 需要多角色、多能力组合
一个类可以同时是Readable、Writable、Closeable,像一个人可以同时有"程序员认证"和"Scrum Master认证"。 - 跨团队、跨模块定义契约
UserService、OrderRepository、PaymentGateway这类对外边界,优先用接口,方便替换实现、Mock 测试。 - 插件、策略、回调、依赖倒置
调用方只依赖《岗位说明书》,不依赖具体员工。
典型:Runnable、Comparable、List、UserService。
三、什么时候用抽象类?
当你在写"岗位族标准手册 + 通用流程模板"时,用抽象类:
- 一组类本质上是同一类东西
狗、猫、鸟都是Animal,有共同字段如name、age,也有共同方法如eat()。 - 需要共享状态或初始化逻辑
抽象类可以有字段、构造器。接口不能有实例状态。 - 需要默认实现,只留钩子给子类
比如AbstractList实现了大量通用逻辑,子类只需实现get()、size()。 - 固定流程,部分步骤可变
模板方法模式:
service()固定"解析请求 -> 鉴权 -> 处理 -> 返回",其中doGet()、doPost()由子类实现。
就像公司规定所有研发岗都走"接需求 -> 开发 -> 自测 -> 提测 -> 上线",但具体开发由前端、后端、算法各自完成。 - 需要 protected、构造器、非 public 扩展点
这些接口做不到,抽象类可以。
典型:AbstractList、HttpServlet、InputStream、BaseService。
四、核心对比及选型(适配 Java8 + 新特性)
- 继承实现:抽象类用
extends,接口用implements;一个类只能继承一个抽象类,但可以实现多个接口。 - 构造方法:抽象类可以有构造器,供子类初始化;接口不能有构造方法。
- 成员变量:抽象类可以有任意修饰符的成员变量;接口里的变量默认是
public static final常量。 - 方法(重点!Java8 改动)
- 抽象类:可以有抽象方法、普通实例方法、静态方法。
- 接口:Java8 之前只能有抽象方法;Java8 新增
default默认方法、static 静态方法;Java9 + 支持私有方法。
- 修饰符:抽象类方法可以用 public/protected/private;接口抽象方法默认 public。
怎么选?决策口诀
- 只定职责、能力、契约:接口。
- 要带共享状态、通用代码、SOP 模板:抽象类。
- 多个不相关类只是"都能做某事":接口。
- 多个类"本质同一族",还要复用代码:抽象类。
- 需要多继承能力:接口。
- 需要模板方法、固定流程:抽象类。
- 既要对外契约,又要内部复用:接口 + 抽象类组合。
比如支付场景:
java
interface Payable {
void pay(BigDecimal amount);
}
abstract class AbstractPayment implements Payable {
public final void pay(BigDecimal amount) {
validate();
doPay(amount);
log();
}
protected abstract void doPay(BigDecimal amount);
protected void validate() { /* 通用校验 */ }
protected void log() { /* 通用日志 */ }
}
class WechatPay extends AbstractPayment {
protected void doPay(BigDecimal amount) {
// 微信支付具体实现
}
}
这里:
Payable是《岗位说明书》:必须能支付。AbstractPayment是《支付岗位族手册 + SOP》:校验、支付、日志流程固定,具体渠道动作由子类填。WechatPay是具体员工:继承岗位族,完成具体工作。
五、 最爱踩的坑
坑 1:认为接口只能写抽象方法
typescript
// Java8+合法,接口可以有默认方法
public interface Logger {
default void printLog(String msg){
System.out.println("日志:" + msg);
}
}
坑 2:分不清选型,有公共属性就无脑写接口
接口中定义的变量是常量,不能被子类修改。 如果子类需要拥有自己独有的成员属性,不要用接口,优先抽象类。
坑 3:为了复用代码强行使用抽象类
Java 是单继承,一旦继承抽象类,就不能再继承别的类。 如果只是定义能力规范,优先接口,不要占用唯一的继承名额。
✅选型建议
- 只想定义「具备什么能力」,无公共属性:接口
- 有大量共用字段、共用基础逻辑,需要半成品模板:抽象类
高频面试辨析
Q1:接口和抽象类都不能实例化? A:是的,两者都不能直接 new 创建对象。
Q2:接口的默认方法可以被重写吗? A:可以,子类实现接口后,可以重写 default 默认方法。
Q3:抽象类一定包含抽象方法吗? A:不一定。抽象类可以全部都是普通方法,只要加 abstract 修饰就是抽象类。
✅核心总结
- 接口:定义能力规范,多实现,变量默认常量,Java8 支持 default 默认方法。
- 抽象类:半成品模板,单继承,可以拥有实例变量、构造器。
- 选型原则:能力规范用接口;有共用属性、基础模板逻辑用抽象类。
- Java8 之后老八股失效,面试不要死记旧版本答案。
最终记住:
接口 = 岗位说明书 / 能力认证,解决"能做什么、谁都能挂这个角色"。
抽象类 = 岗位族手册 + SOP + 档案模板,解决"同一类东西怎么复用代码和流程"。
优先用接口定义契约;确实有共同状态和模板流程时,再用抽象类。
📝本文属于专栏「后端基础拾光集」系列。 这个专栏结合生活化案例、线上踩坑实例讲解 Java 基础。 同系列已经更新:ArrayList & LinkedList 源码、HashMap、红黑树、Java 泛型、异常体系、Integer 缓存池、equals & hashCode、static、final。 专栏持续更新 Java 后端基础、源码、面试踩坑干货,欢迎一起交流后端学习、面试踩坑经验。