前言:关于"套娃"代码的终极拷问
坐标:图书馆 5 楼(靠空调位,今天风很大)
状态:刚改完一个被无限层级继承搞崩的 Bug。
碎碎念: 兄弟们,今天我彻底破防了。 本来想给我的项目加个新功能,结果发现原作者(也就是上个月的我)写了一堆深不见底的继承。我想改个方法,结果十几个子类全报错。 学长过来看了一眼,摇摇头说:"你这设计,属于是'过度继承',这种场景你应该用接口(Interface)而不是抽象类(Abstract Class)啊。"
回来后我反思了很久。原来这两者虽然都能用来"被继承",但底层逻辑完全不同。今天咱就来聊聊这两大"模板",看看它们到底怎么选!
🚀 目录
-
[抽象类 (Abstract Class):未完成的"毛坯房"](#抽象类 (Abstract Class):未完成的“毛坯房”)
-
1.1 什么是抽象类?
-
1.2 它是为了"共性"而生
-
-
[接口 (Interface):一份必须遵守的"协议"](#接口 (Interface):一份必须遵守的“协议”)
-
2.1 什么是接口?
-
2.2 它是为了"扩展"而生
-
-
[哲学思考:is-a 与 has-a 的博弈](#哲学思考:is-a 与 has-a 的博弈)
-
4.1 抽象类是"亲爹"
-
4.2 接口是"考证"
-
-
[JDK 8 之后的"叛变":接口居然能写实现了?](#JDK 8 之后的“叛变”:接口居然能写实现了?)
1. 抽象类 (Abstract Class):未完成的"毛坯房"
生活化场景: 学校要开发一个"学生管理系统"。所有学生(无论是 CS 的、经管的、还是艺术的)都有学号、名字,都要选课。 但不同专业的学生"写作业"的方式完全不一样。
这时候,我们可以搞一个 Student 的抽象类。
1.1 什么是抽象类?
它是一个半成品。
-
它不能被
new出来(你见过一个纯粹的、没有任何属性的"学生"对象吗?没有,他必须是某个具体的专业生)。 -
它里面可以有已经写好的方法(大家共用的,比如
sleep()),也可以有没写完的方法(abstract方法,比如study())。
1.2 它是为了"共性"而生
抽象类强调的是**"身份(Identity)"**。它是子类的模板,子类是对它的细化。
abstract class Student {
String name;
// 共性方法:大家都得睡觉
void sleep() {
System.out.println("在图书馆睡着了...");
}
// 抽象方法:具体怎么学,子类自己定
abstract void study();
}
2. 接口 (Interface):一份必须遵守的"协议"
生活化场景: 不论你是学生、老师,还是校外的路人。只要你想进校门,你就得有"进校证(CanEnterSchool)"。 这个证件规定你必须具备"刷码(swipeCard)"和"出示健康信息(showInfo)"的功能。
2.1 什么是接口?
它是一份契约 或者规范。
-
接口只管"能干什么",不管"怎么干"。
-
它里面全都是没有实现的"大饼"(除非是 JDK 8 以后的
default方法)。
2.2 它是为了"扩展"而生
接口强调的是**"功能(Ability)"**。一个类可以实现多个接口。
-
大学生比喻:我可以是一个"学生(抽象类继承)",同时我也可以拥有"英语四级(接口实现)"、"驾照(接口实现)"。
interface DrivingLicense {
void drive(); // 只要有驾照,你就得会开车
}
3. 核心对撞:一张表分胜负
| 特征 | 抽象类 (Abstract Class) | 接口 (Interface) |
|---|---|---|
| 关键字 | abstract class |
interface |
| 关系 | extends (继承) |
implements (实现) |
| 数量 | 单继承(只能有一个亲爹) | 多实现(可以考很多证) |
| 变量 | 可以有普通变量 | 只能是常量 (public static final) |
| 构造器 | 有(供子类调用) | 无 |
| 方法 | 可以有具体实现,也可以抽象 | 以前全是抽象,现在可以有 default |
4. 哲学思考:is-a 与 has-a 的博弈
这是理解两者的灵魂所在。
4.1 抽象类是"is-a"
如果你觉得 A 是一个 B,那就用抽象类。
-
猫 是一个 动物。
-
CS学生 是一个 学生。
4.2 接口是"has-a"或"like-a"
如果你觉得 A 具有某种功能,那就用接口。
-
猫 具有 爬树的能力。
-
这个类 具有 被序列化的能力(Serializable)。
5. JDK 8 之后的"叛变":接口居然能写实现了?
在很久以前,接口是纯粹的。但从 JDK 8 开始,为了支持 Lambda 表达式和方便版本升级,接口里可以写 default 方法和 static 方法了。
这就尴尬了:接口越来越像抽象类了? 其实不然。即便接口能写实现,它依然没有状态(成员变量) ,依然支持多实现。这些核心本质没变。
6. 大厂面试:什么时候该用哪一个?
面试官最喜欢问:"既然接口现在能写实现,那还要抽象类干嘛?"
你可以这样装杯(回怼):
-
如果你需要定义子类的共同属性(比如名字、年龄),抽象类是唯一的选择,因为接口不能存数据(只有常量)。
-
如果你需要约束不同类别的行为(比如灯能开关、电视也能开关),请用接口。
-
如果你想预留后路,建议用接口。因为 Java 是单继承,用了抽象类就把"亲爹位"占了,以后想改就难了。
7. 结语
写完这篇日记,我终于把之前的"套娃"代码重构完了。 我把那些乱七八糟的继承改成了接口组合,代码瞬间感觉清爽了许多。
作为大学生的我们:
-
抽象类教我们要有深度(扎实的根基)。
-
接口教我们要有广度(多样的技能)。
如果你也被"到底用哪个"纠结过,点个赞支持一下!下期咱们聊聊Java的异常体系