2.1 族谱登记:继承与方法重写


2.1 族谱登记:继承与方法重写

本节摘要:继承用 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 链) 子类改写祖产行为

一句话分辨:重载是"一个名字多种入参",重写是"一份章程后代改版"。绑定时机那一列是分水岭——重载在编译期就定了调谁,重写要等运行时看对象是谁,这正是下一节多态的入口。

图 2-1 方法调用在族谱上的查找路径

图 2-1 方法调用在族谱上的查找路径

完整案例:族谱上的一次安全重构

背景:物流系统里有 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 章接口的用武之地)——继承适合"家族内少量差异",不适合"差异维度无限增长"。

本节要点回顾

  • 继承共享祖产:子类自动获得父类非私有成员,Java 单继承、构造器不被继承,子类构造器第一行必须处理 super(显式或隐式)
  • 父类无参构造器是隐式 super 的目标:只写带参构造器会让子类全线报错——1.2 节的事故在族谱上放大
  • 重写三规则:签名相同、访问不能更严、返回可协变;@Override 让编译器替你把关,务必养成手写习惯
  • super 两用法:super(参数) 走父类入籍链,super.方法 回望祖先流程;"先祖先后自己"是最常用的重写姿势
  • 重载与重写一句话分工:重载编译期按入参静态选、重写运行期按对象动态定——后者的机制正是下一节的主角

族谱登记完毕,同一张叫号单下不同住户如何各自应答?下一节把"父类引用指向子类对象"这个写法拆开,看动态绑定在运行时怎么走。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U