本节摘要:继承用 extends 把类连成族谱——子类自动获得父类的非私有成员,可新增自己的成员,也可重写父类方法。方法重写有硬规则:方法签名相同、访问权限不能更严、返回类型可以协变;super 则是子类回望祖先的通道。本节拆解继承拿了什么不能拿什么、构造器在族谱上的传递链,并厘清重载与重写这对最常被混谈的概念。
上一章我们能把 Resident、Account 各自登记得清清楚楚,但真实系统里会出现一族高度相似的类:Employee、Manager、Intern 都有姓名、工号、都能打印名片,差异只在一两处行为。复制三遍代码的代价很快显形:改一处打印格式,要记得改三个类,第四个类是后来人写的、照着旧版抄,格式从此不一致。继承就是解决这个问题的机制:把公共成员登记到父类,子类 extends 之后自动持有,只需声明差异部分。
// 祖产登记在父类 class Staff { protected String name; // protected:给跨包子孙留的通道(第 1 章矩阵) protected int no; Staff(String name, int no) { this.name = name; this.no = no; } void printCard() { // 通用章程 System.out.println("工号 " + no + " :" + name); } } // 子类一:经理 —— 祖产全继承,再加自己的成员 class Manager extends Staff { double bonus; Manager(String name, int no, double bonus) { super(name, no); // 族谱上的入籍链:先请祖先完成登记 this.bonus = bonus; } void grantBonus() { // 子类新增技能 System.out.println(name + " 发放津贴 " + bonus + " 元"); } } public class ExtDemo { public static void main(String[] args) { Manager m = new Manager("周九", 1001, 5000.0); m.printCard(); // 祖产方法直接可用 输出:工号 1001 :周九 m.grantBonus(); // 输出:周九 发放津贴 5000.0 元 } }
继承了两条铁律要立即立住。其一,Java 单继承:一个类只能有一个直接父类(一个家族只能入一本族谱),但一个父类可以有很多子类;"一个人身兼多职"的需求由接口承担(第 3 章)。其二,构造器不被继承:子类构造器必须在第一行处理父类的入籍——要么显式 super(参数),要么省略(编译器隐式插入 super() 调父类无参构造器)。这直接呼应 1.2 节的事故:父类只写了带参构造器、无参版消失,子类那句隐式的 super() 就找不到目标,全线报错。
继承来的方法如果行为不合身,子类可以重写(override):方法签名相同、重新给实现。三条硬规则:签名必须与父类一致(方法名与参数列表);访问权限不能比父类更严(父类 protected,子类最多提到 public、不能压回 private);返回类型要么相同、要么是父类返回类型的子类(协变返回)。配上 @Override 注解,编译器会替你核对"确实重写了父类方法"——注解不写程序也能跑,但写错方法名时没有注解就变成定义新方法,这种静默错误排查极苦。
class Intern extends Staff { Intern(String name, int no) { super(name, no); } @Override void printCard() { // 重写:实习生名片加标注 super.printCard(); // 回望祖先:先按通用章程打印 System.out.println(" 备注:实习期成员,权限受限"); // 再追加自己的章程 } } public class OverrideDemo { public static void main(String[] args) { Intern i = new Intern("吴十", 2001); i.printCard(); // 输出: // 工号 2001 :吴十 // 备注:实习期成员,权限受限 } }
这里出现了 super 的两种用法:super(参数) 在构造器第一行调父类构造器;super.方法名 在子类方法里调父类版本。重写不等于替换——"先走祖先流程、再补自己的"是极常见的重写姿势,模板方法模式(第 3 章 3.1 节)把这个姿势机制化了。
重写最容易被拿来与**重载(overload)**混为一谈,两者只差一个字,机制完全不同:
| 对比项 | 重载 Overload | 重写 Override |
|---|---|---|
| 发生位置 | 同一个类里(含父类继承来的) | 子类与父类之间 |
| 方法签名 | 同名、参数列表必须不同 | 完全相同 |
| 绑定时机 | 编译期按参数静态选择 | 运行期按实际对象动态绑定(2.2 节) |
| 访问权限 | 无限制 | 不能比父类更严 |
| 典型场景 | 构造器多口径(1.2 节 this 链) | 子类改写祖产行为 |
一句话分辨:重载是"一个名字多种入参",重写是"一份章程后代改版"。绑定时机那一列是分水岭——重载在编译期就定了调谁,重写要等运行时看对象是谁,这正是下一节多态的入口。

背景:物流系统里有 PlainPackage(普通件)和 FragilePackage(易碎件),最初是两个独立类,打印面单的代码各写一份。面单格式调整时漏改易碎件,线上打出了旧格式。操作:抽取父类 Package 持有公共字段与面单框架方法,易碎件重写面单方法并在其中先调 super:
class Package { protected String id; Package(String id) { this.id = id; } String label() { return "包裹 " + id + " 请轻放"; } } class FragilePackage extends Package { FragilePackage(String id) { super(id); } @Override String label() { return super.label() + " 【易碎 加固包装】"; } } public class RefactorDemo { public static void main(String[] args) { System.out.println(new Package("P01").label()); // 输出:包裹 P01 请轻放 System.out.println(new FragilePackage("F09").label()); // 输出:包裹 F09 请轻放 【易碎 加固包装】 } }
结果:面单通用文案只存在于父类一处,改一处两个类同时生效。解读:继承复用的是"结构加行为",重构收益来自公共逻辑的单一事实来源;重写里调用 super 保留了家族通用流程,只做增量修饰。变式:若以后出现"冷链件"要叠加第三种标注,再派一个子类重写即可;若标注种类爆炸成十几种,就该换成"装饰"思路或策略接口(第 3 章接口的用武之地)——继承适合"家族内少量差异",不适合"差异维度无限增长"。
族谱登记完毕,同一张叫号单下不同住户如何各自应答?下一节把"父类引用指向子类对象"这个写法拆开,看动态绑定在运行时怎么走。