元数据卡
- 前置知识:第7章(类与对象、封装)
- 预计时间:15 分钟
- 阅读模式:概念理解
- 完成标志:能用
extends消除重复代码,理解 is-a 关系
你的进度
你在变量村的铁匠铺里蹲了一个星期了。老陈师傅教了你如何把属性(字段)和行为(方法)打包成"类"这种工具箱,还教会了你用 private 锁住关键零件——你已经有封装的概念了。
但这两天你在做一个麻烦的活儿。
村口需要一套"通信系统":驿站跑马传信、信鸽从空中送信、村头大喇叭喊话。嗯,你手上已经有了几个类——Horse、Pigeon、Loudspeaker。每个类都有自己的 send() 方法,但代码里面有大量重复的东西:
- 都有"消息内容"
- 都要"格式化"
- 都要"记录发送时间"
你复制粘贴了三次,现在 Horse 里改了一行,得去 Pigeon 和 Loudspeaker 里同样改一遍。你已经忘了昨晚改了哪个没改哪个。
"你这孩子,"老陈师傅叹了口气,拿起一块铁胚,"不学会站在别人的基础上造东西,你一辈子都在打一模一样的锄头。"
他把那块铁胚放在你面前:"这叫继承。"
你的任务
旧办法:复制粘贴做三个相似的类。问题在于——改一处要改三处,加一处要加三处,一个 bug 可以潜伏两份你没改的副本里。
这一章要解决的问题很简单:能不能只写一次公共逻辑,让其他类型说"我继承它"?
第一幕:extends——站在前人的肩膀上
你有三个类:Horse、Pigeon、Loudspeaker,它们都共享"消息"这个公共概念。你把公共部分抽出来,放到一个叫 MessageSender 的类里:
// MessageSender.java — 公共消息发送能力
public class MessageSender {
String message;
long timestamp;
public MessageSender(String message) {
this.message = message;
this.timestamp = System.currentTimeMillis();
}
public void send() {
System.out.println("[LOG] 发送时间: " + timestamp);
System.out.println("消息: " + message);
}
}语言:Java 17+ 如何运行:保存为 .java 文件,用 javac 编译后,用 java 运行
现在,Horse 不再从零开始写了——它 extends(继承)MessageSender:
// Horse.java — 继承 MessageSender
public class Horse extends MessageSender {
private String riderName;
public Horse(String message, String riderName) {
super(message); // 调用父类构造方法,必须先写
this.riderName = riderName;
}
@Override
public void send() {
super.send(); // 先执行父类的公共行为
System.out.println("骑士 " + riderName + " 策马奔腾!");
}
}语言:Java 17+ 预期输出:调用 new Horse("敌军来袭", "张三").send() 时,会输出时间戳、消息,然后输出"骑士 张三 策马奔腾!" 你试试:把 super(message) 注释掉,看编译器报什么错
super 就是你喊的那声"爸,干活了"——它引用父类。super(message) 调用父类构造器,super.send() 调用父类被重写前的方法。
信鸽也是继承:
// Pigeon.java — 继承 + 不同的 send 实现
public class Pigeon extends MessageSender {
private int speedLevel; // 1 = 普通, 2 = 加急
public Pigeon(String message, int speedLevel) {
super(message);
this.speedLevel = speedLevel;
}
@Override
public void send() {
super.send();
String level = speedLevel == 2 ? " 加急!" : " 普通";
System.out.println(level + " 信鸽起飞!");
}
}预期输出:new Pigeon("天气转晴", 2).send() → 先打印时间戳和消息,再打印" 加急!信鸽起飞!"
常见陷阱
陷阱一:继承滥用——"我是你的子类"不等于"我是你的"
新手最大的坑:为了复用方法而继承。
// 错误示范
public class Dog extends ArrayList<Toy> { // 狗是一堆玩具???
public void bark() { System.out.println("汪!"); }
}狗有玩具,但狗不是玩具的列表。继承表达的是 "is-a"(是一个)关系,不是 "has-a"(有一个)关系。
正确做法是用组合(composition):
// 正确示范
public class Dog {
private List<Toy> toys = new ArrayList<>(); // 狗有玩具
public void bark() { System.out.println("汪!"); }
}陷阱二:super 链——构造方法要按顺序来
public class Loudspeaker extends MessageSender {
public Loudspeaker(String message) {
// 如果不写 super(message),编译器调用 super()——但 MessageSender 没有无参构造器
// 编译错误!
}
}规则:子类构造方法必须在第一行调用父类构造方法。如果你不写,编译器会插入 super()(调用父类无参构造)。如果父类没有无参构造,编译报错。
链式调用顺序:当你 new Horse(...) 时,实际调用链是:
MessageSender的构造器先执行- 然后
Horse的构造器再执行
这叫构造链——从最顶层的父类一路往下。
旅人笔记
继承消除重复代码。
super调用父类构造器或父类方法。 继承表达 is-a,组合表达 has-a。 构造链从父类到子类自上而下执行。
→ 下一站预告
继承让你消除了重复代码。但更强大的能力藏在它的背后——多态:写一段代码,让它自动适配不同的子类类型。