4.3 抽象类和接口
抽象类和接口是两种"立规矩"的工具:抽象类是半成品图纸(公共实现 + 强制子类补的空格子),接口是纯能力合同(只列要求不给实现)。它们把"父类说了算"升级成"父类只管要求,实现归子类"——多态的前菜。
1. 先看继承的一个尴尬:父类的方法没法写
4.1 的 Animal 里有个 sound(),当时没写。现在试着写写看:
class Animal {
void sound() {
??? // 狗汪汪、猫喵喵、鸟啾啾……写哪个都错
}
}
父类里根本没有"正确答案"——叫声是每个子类自己的事。两种凑合写法都有毛病:
- 写个
System.out.println("...")占位 → 子类忘了重写,程序默默输出错误内容,不报错,最难查; - 写个空方法体 → 同样不报错,还是没人发现子类漏了。
根本矛盾:我们想要的是——"动物必须会叫"这条规矩留在父类,但怎么叫必须由子类自己写,而且谁漏写就让谁的代码编译不过。
这就是 abstract(抽象)登场的地方。
2. 抽象类:半成品图纸
abstract class Animal { // ① 类加 abstract
String name;
int age;
void breathe() { // ② 普通方法照常实现(白得的部分)
System.out.println(name + "在呼吸");
}
abstract void sound(); // ③ 抽象方法:只有签名,没有方法体
}
抽象方法长这样:abstract void sound();——直接分号结尾,连 { } 都没有。意思是:"这条规矩必须兑现,怎么兑现我不管。"
四条硬规矩(编译器盯着)
| # | 规矩 | 原因 |
|---|---|---|
| 1 | 类里有抽象方法,类必须声明为 abstract | 有没兑现的规矩,就不是完整图纸 |
| 2 | 抽象类不能 new | 半成品造不出能用的实物——new Animal() 编译报错 |
| 3 | 子类必须重写全部抽象方法,否则子类自己也得是 abstract | 兑现不完就接着当下一个半成品 |
| 4 | 抽象类可以没有抽象方法(就是不想被 new 而已) | 有时锁 new 本身就是目的 |
规矩 3 是本节的精髓:编译器代替你检查"子类有没有兑现规矩"——之前"忘了重写不报错"的隐患,被连根拔掉。
class Dog extends Animal {
void sound() { // 不写它,Dog 直接编译报错
System.out.println(name + "汪汪叫");
}
}
不能 new ≠ 没有构造。子类创建时依然会先执行父类构造(给公共属性赋值)——"构造方法不被继承但会被调用",4.1 埋的伏笔,下节 super 收网。
3. 接口:纯能力合同
抽象类还是有"私货":属性、实现好的方法。接口干脆全扔掉,只留合同:
interface Flyable { // ① 关键字是 interface,不是 class
void fly(); // ② 方法自动是 public abstract,不用写
int MAX_HEIGHT = 10000; // ③ 属性自动是 public static final 常量
}
class Bird extends Animal implements Flyable { // ④ implements 兑现合同
void sound() { System.out.println(name + "啾啾叫"); }
public void fly() { // ⑤ 必须写 public,少写编译报错
System.out.println(name + "飞到 " + MAX_HEIGHT + " 米");
}
}
接口的关键词都是"自动":
- 方法 → 自动
public abstract(全是要兑现的规矩) - 属性 → 自动
public static final(全是常量,4.2 的三件套默认送齐) - 没有构造方法,不能 new
接口最大的本事:弥补单继承
4.1 说过 Java 单继承——爹只能一个。但合同可以签很多份:
class Bird extends Animal implements Flyable, Singable {
// 一个爹 + N 份能力合同,全都能要
}
为什么接口能多签?因为接口只有规矩没有实现,不存在"两个爹打架"——规矩冲突时反正要你自己兑现,编译器只管检查你交没交作业。4.1 说的"Java 用单继承 + 接口绕开多继承的坑",就是这。
注意实现接口时的一个细节:实现方法必须写 public——接口方法默认是 public,你实现时权限只能放宽不能收紧,不写就是默认权限,直接报错。
4. 抽象类 vs 接口:一张表定选择
| 抽象类 | 接口 | |
|---|---|---|
| 关键字 | abstract class |
interface |
| 挂上去 | extends(只能一个) |
implements(可以多个) |
| 里面装什么 | 什么都行:属性、实现方法、抽象方法 | 常量 + 抽象方法(纯合同) |
| 有构造方法吗 | 有(给子类用) | 没有 |
| 回答的问题 | 你是什么(is-a) | 你会什么(can-do) |
| 典型例子 | Animal:动物家族的爹 | Flyable:会飞的能力 |
选择口诀一句话:
共性(家族血缘 + 公共实现)→ 抽象类
能力(跨家族的通用本事)→ 接口
拿现实检验:鸟和飞机——"飞机是一种鸟"?荒谬,血缘不同。但"都会飞"是共同能力 → 飞的能力就该做成 Flyable 接口,鸟和飞机各自 implements。而狗、猫、鸟的公共属性和呼吸 → 走 Animal 抽象类。is-a 用抽象类,can-do 用接口,面试和实战都是这句。
5. 和老知识的三处接头(复习锚点)
- 抽象方法必须被重写 → "重写"的正式规则(同名同参数、访问权限只能放宽)就在下节——本节你已经实战用了一堆,下节补理论;
- 接口属性自动 static final → 4.2 常量三件套的"自动版";
- extends 一个 + implements 多个 → 4.1 单继承规矩的完整解法(爹一个,合同随便签)。
6. 动手练习(保姆式,接着 IDEA04 项目写)
① 打开 IDEA04,src 右键 → New → Java Class → 输入 Animal,替换成第 2 节的抽象类版本(原 class 前加 abstract,bark 换成抽象 sound)
② src 右键 → New → Java Class → 输入 Flyable——注意列表里选 Interface(默认选的是 Class,别选错)
③ 把 Dog 改成重写 sound():void sound() { System.out.println(name + "汪汪叫"); }
④ 新建 Bird 类抄第 3 节的 Bird(extends Animal implements Flyable)
⑤ 新建 Test01 跑通:
public class Test01 {
public static void main(String[] args) {
Animal a = new Dog(); // 父类钥匙指子类车——先混脸熟,下节讲透
a.name = "旺财";
a.breathe();
a.sound(); // 旺财汪汪叫
Bird b = new Bird();
b.name = "翠花";
b.sound(); // 翠花啾啾叫
b.fly(); // 翠花飞到 10000 米
}
}
成功标准:四行输出一字不差。
三个 30 秒实验(每个都值得做):
- new 抽象类:把
new Dog()临时改成new Animal()——看"抽象类不能实例化"的报错长什么样; - 漏兑现合同:把 Bird 的
fly()整个删掉——看编译器怎么拦"没兑现合同"的类(红线 + 报错里出现 implements 字样); - 少写 public:把
public void fly()的 public 去掉——看"权限不能比接口更严格"的报错。
7. 常见误区
不用。没有任何抽象方法的抽象类完全合法——目的就是不许别人 new,只许当爹。规矩 4。
有!而且必须给子类调用。不能 new 和没有构造是两回事——构造是给"儿子出生时先给爹签到"用的。
接口不是类,是独立的类型。它没有构造方法、不能 new、成员全是常量——用 class 的眼光看接口哪哪都别扭。
不行。接口方法默认 public,实现时权限只能放宽不能收紧——不写就是默认权限,比 public 窄,编译报错。动手实验 3 现场验证。
两者分工不同:抽象类管"家族共性 + 公共实现",接口管"跨家族能力"。成熟的设计往往是抽象类打地基、接口装能力——Bird extends Animal implements Flyable 就是标准姿势。
8. 自测题(先自己答,再展开)
空方法体"忘了重写也不报错",错误会逃到运行时变成逻辑 bug;abstract 让编译器强制检查——子类没兑现就编译不过。把"忘写"从运行时事故提前到编译期拦截,这是抽象类的核心价值。
不能 new。但构造方法给子类用——子类对象创建时会先执行父类构造,给继承来的公共属性赋值。"不能实例化"和"没有构造方法"是两码事。
合法。继承是单选(Animal 只能一个爹,规矩不变),接口是多选(合同随便签,Flyable、Singable 都能要)。这正是 Java"单继承 + 多实现"绕开多继承冲突的完整解法。
自动是 public static final——public 谁都能用(合同是公开的)、static 属于接口本身不用 new(3.7)、final 不许改(4.2)。接口里的"变量"其实全是常量。
都是接口。"会移动""会飞"是跨家族的能力(can-do)——狗、飞机血缘完全不同,硬做成抽象类就得问"飞机是一种狗吗",is-a 念不通。能力做成接口(Movable、Flyable),谁有这本事谁 implements。
9. 下一步学什么
- ☑ 4.4 多态 ✅ —— 伏笔转正完成:三个前提、编译看左运行看右、属性无多态特例、向上/向下转型与 instanceof、
ClassCastException登场(三大新手异常集齐)。点此复习 - ☐ 复习提示:把动手实验 1~3 全做一遍,尤其实验 2(漏兑现合同)——"编译器强制兑现"这个感觉建立了,抽象类和接口就真懂了
相关笔记
- 4.1 继承 ← extends 的规矩本节全部生效;"单继承 + 接口"的解法在此收网
- 4.2 final关键字 ← final 锁死不许改,abstract 强制必须改——一个踩刹车一个踩油门
- 3.7 static关键字 / 4.2 final关键字 ← 接口属性"自动三件套"的来源
- 📇 Java基础 · 索引
- 🏠 返回主页