摘要: 本文深入讲解Java面向对象编程中的继承机制,从重复代码问题引入,详细解析extends关键字、super用法、方法重写规则、构造方法调用链等核心概念。通过学校人员管理系统等实际案例,展示如何通过继承实现代码复用和层次化设计,帮助初学者掌握继承的适用场景与设计考量,为理解多态打下坚实基础。
关键词: Java继承, extends关键字, super关键字, 方法重写, 面向对象编程
在上一篇文章里,我们给代码装上了一道“防护门”——封装。通过把属性设为 private,只暴露受控的方法,我们的类变得安全了、有自我意识了。
今天,我们要给面向对象的大厦添上第二根顶梁柱——继承(Inheritance)。
如果说封装是“横向”地把数据和操作放在一起,那继承就是“纵向”地组织类与类之间的关系,让代码能够一层一层地复用。
1. 一个熟悉的场景:重复的代码
假设你是学校信息系统的开发者。你需要定义几种角色:Student(学生)、Teacher(老师)、Staff(行政人员)。
你会发现,这三者有大量共同的信息:姓名、年龄、联系方式、ID。如果你独立定义三个类,这些字段和方法每个类都要写一遍:
public class Student {
private String name;
private int age;
private String phone;
private String studentId;
// getter/setter 大量重复...
}
public class Teacher {
private String name;
private int age;
private String phone;
private String employeeId;
// getter/setter 还是大量重复...
}
public class Staff {
private String name;
private int age;
private String phone;
private String department;
// 又是大量重复...
}
你发现了问题:代码在重复。修改一个字段的名字,要改三个类;增加一个公共属性,也要改三个类。这不优雅,也不高效。
这时你就会想:能不能把“人”的共同特征提取出来,让 Student、Teacher、Staff 直接“继承”这些共同的东西,然后再加上自己特有的内容?
这就是继承要解决的问题。
2. 什么是继承?—— “父子”关系的建立
继承(Inheritance) 是一种机制,它允许一个类(子类)基于另一个类(父类)来定义,从而获得父类的属性和方法,并可以在此基础上扩展或修改。
- 父类(Superclass / Parent Class):被继承的类,也叫基类。它包含了通用的属性和行为。
- 子类(Subclass / Child Class):继承父类的类,也叫派生类。它拥有父类的全部属性和方法(除了
private的),并可以增加自己特有的内容。
打个比方:你觉得“动物”是一个比较宽泛的概念。动物有名字、会吃东西、会移动。在这个基础上,你定义“狗”是动物的一个特例——狗继承了动物的全部特征(名字、吃、移动),但额外增加了“吠叫”的能力。“猫”也继承了动物,但增加了“捉老鼠”的能力。这就是继承:从“一般”到“特殊”的延伸。
用专业术语说:子类是一种(is-a)父类。狗是一种动物,学生是一个人。继承关系表达的是一种 “is-a” 关系。
3. 继承的语法 —— extends
在 Java 中,继承使用 extends 关键字。
// 父类(基类)
public class Person {
private String name;
private int age;
private String phone;
// 构造方法
public Person(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
// ...
}
然后定义子类,用 extends 继承 Person:
// 子类:Student 继承 Person
public class Student extends Person {
// 新增属性:学号
private String studentId;
// 构造方法
public Student(String name, int age, String phone, String studentId) {
super(name, age, phone); // 调用父类的构造方法
this.studentId = studentId;
}
// 新增方法:学习
public void study() {
System.out.println(getName() + " 正在学习...");
}
}
// 子类:Teacher 继承 Person
public class Teacher extends Person {
private String employeeId;
public Teacher(String name, int age, String phone, String employeeId) {
super(name, age, phone);
this.employeeId = employeeId;
}
public void teach() {
System.out.println(getName() + " 正在讲课...");
}
}
现在,Student 和 Teacher 都自动拥有了 Person 的 name、age、phone 属性以及它们的 getter/setter,不需要再重复定义了。它们只需要关注自己特有的部分。
4. super 关键字 —— 指向“父辈”的引用
在上面的代码中,你可能已经注意到了 super(name, age, phone); 这个看起来有些神秘的调用。我们来拆解一下。
① super() —— 调用父类的构造方法
子类构造方法的第一条语句,必须调用父类的构造方法(使用 super(...)),以确保父类的属性先被正确初始化。
如果父类有无参构造方法,并且子类没有显式调用 super(...),Java 会自动帮你调用父类的无参构造。但如果父类只有有参构造(没有无参构造),子类必须显式地用 super(...) 调用。
public class Person {
private String name;
// 没有无参构造,只有有参构造
public Person(String name) {
this.name = name;
}
}
public class Student extends Person {
private int score;
public Student(String name, int score) {
super(name); // 必须显式调用父类的有参构造
this.score = score;
}
}
⚠️ 如果父类既有无参构造又有有参构造,子类可以不用显式调用
super(),但如果你需要传入参数,必须显式调用。
② super.xxx —— 访问父类的成员
当子类中定义了和父类同名的属性或方法时,super.xxx 用来明确访问父类的版本。
public class Person {
protected String name = "Person";
public void sayHello() {
System.out.println("Hello from Person");
}
}
public class Student extends Person {
private String name = "Student";
public void test() {
System.out.println(super.name); // 输出 "Person"
System.out.println(this.name); // 输出 "Student"
super.sayHello(); // 调用父类的方法
}
}
注意:
super和this一样,不能用于静态上下文中(因为它们是针对实例的)。
5. 方法重写(Override) —— 子类的“个性化定制”
子类继承了父类的方法,但如果父类的方法在子类的语境下“不适用”,子类可以重新定义该方法——这就是方法重写(Override)。
public class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
public class Dog extends Animal {
@Override // 注解,告诉编译器这是重写,帮你检查
public void sound() {
System.out.println("汪汪汪!");
}
}
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("喵喵喵~");
}
}
使用:
Animal a1 = new Dog();
a1.sound(); // 输出:汪汪汪!
Animal a2 = new Cat();
a2.sound(); // 输出:喵喵喵~
重写的规则
- 方法名、参数列表必须完全一致(返回类型可以是父类返回类型的子类型,称为“协变返回类型”,初学暂不深入)。
- 访问修饰符不能比父类更严格(比如父类是
public,子类不能是private)。 final方法不能重写,static方法不能被重写(静态方法属于类,没有“重写”的概念,只能说“隐藏”)。
@Override 注解
@Override 是一个注解,它不改变代码的运行,但给编译器传达了信息:“我要重写父类的方法,请帮我检查一下方法签名是否正确。”如果你写错了(比如拼写错误或参数类型不对),编译器会报错。这是一个很好的习惯,一定要加。
6. 继承中的构造方法调用链 —— 从顶到底
当创建一个子类对象时,JVM 会调用父类的构造方法,然后调用子类的构造方法。从继承链的顶端(Object)到最底端(当前类),一层一层地初始化。
public class Grandparent {
public Grandparent() {
System.out.println("Grandparent 构造方法");
}
}
public class Parent extends Grandparent {
public Parent() {
System.out.println("Parent 构造方法");
}
}
public class Child extends Parent {
public Child() {
System.out.println("Child 构造方法");
}
}
// 输出:
// Grandparent 构造方法
// Parent 构造方法
// Child 构造方法
记住:父类的构造方法总是先执行。这保证了父类的属性在子类使用之前已经被正确初始化。
7. 单继承 —— Java 的“一个孩子只有一个亲生父亲”
在 Java 中,一个类只能直接继承一个父类(单继承)。这与其他一些语言(如 C++ 支持多继承)不同。
// 这是合法的
public class Student extends Person { ... }
// 这是非法的(编译错误)
public class Student extends Person, Employee { ... } // ❌
为什么 Java 要这样做?
- 避免“菱形继承”问题:如果 A 有方法 m(),B 和 C 都继承自 A 并各自重写了 m(),D 同时继承 B 和 C,那么 D 应该继承谁的 m()?这种歧义在多继承中很容易出现,Java 通过单继承彻底避免了它。
- 简化设计:单继承让类的关系更清晰,也更容易理解和维护。
但 Java 提供了接口(Interface) 来实现“多重能力”,我们会在第 22 篇文章中详细讲解。接口允许多实现,可以弥补单继承的不足。
8. 什么时候用继承?—— 设计上的考量
继承是一种强耦合的关系,子类对父类有很强的依赖。所以,使用继承时需要谨慎思考:
✅ 适合用继承的场景:
- 子类和父类之间存在明显的“is-a”关系(狗是一种动物、学生是一个人)。
- 子类需要父类的全部或大部分功能,且基本不需要修改父类的行为。
- 你在构建一个框架或公共库,希望用户通过继承来扩展功能。
❌ 不适合用继承的场景:
- 只是为了复用代码(比如恰好都有
name和age字段),但逻辑上不属于“is-a”关系。 - 父类的行为在子类中需要大幅修改(那说明它们可能不应该有继承关系)。
- 深层继承(超过 3 层):继承链过长会让代码难以理解和调试。
著名的设计原则:“优先使用组合而非继承”(Favor composition over inheritance)。但那是进阶话题,初学阶段,我们先掌握好继承这个工具。
9. 综合示例 —— 学校人员管理系统
让我们把今天的内容串起来,写一个完整的例子:
// ========== 父类:Person ==========
public class Person {
private String name;
private int age;
private String phone;
public Person(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
// Getter/Setter(省略部分,避免代码过长)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
// 公共方法
public void introduce() {
System.out.println("我是 " + name + ",今年 " + age + " 岁。");
}
}
// ========== 子类:Student ==========
public class Student extends Person {
private String studentId;
private String major;
public Student(String name, int age, String phone, String studentId, String major) {
super(name, age, phone);
this.studentId = studentId;
this.major = major;
}
public String getStudentId() { return studentId; }
public void setStudentId(String studentId) { this.studentId = studentId; }
public String getMajor() { return major; }
public void setMajor(String major) { this.major = major; }
// 重写 introduce 方法,增加学生特有的信息
@Override
public void introduce() {
super.introduce(); // 调用父类的方法
System.out.println("我是学生,学号 " + studentId + ",专业 " + major);
}
public void study() {
System.out.println(getName() + " 正在学习 " + major);
}
}
// ========== 子类:Teacher ==========
public class Teacher extends Person {
private String employeeId;
private String title; // 职称
public Teacher(String name, int age, String phone, String employeeId, String title) {
super(name, age, phone);
this.employeeId = employeeId;
this.title = title;
}
// Getter/Setter 省略...
@Override
public void introduce() {
super.introduce();
System.out.println("我是老师,工号 " + employeeId + ",职称 " + title);
}
public void teach() {
System.out.println(getName() + " 正在授课");
}
}
// ========== 主程序 ==========
public class Main {
public static void main(String[] args) {
Student s = new Student("张三", 20, "13800001111", "S001", "计算机科学");
Teacher t = new Teacher("李老师", 35, "13800002222", "T001", "教授");
System.out.println("=== 学生信息 ===");
s.introduce();
s.study();
System.out.println("\n=== 教师信息 ===");
t.introduce();
t.teach();
// 多态的体现:把子类对象当作父类来使用
System.out.println("\n=== 多态演示 ===");
Person p1 = new Student("王五", 22, "13800003333", "S002", "数学");
p1.introduce(); // 动态绑定:调用的是 Student 的 introduce()
// p1.study(); // 编译错误!Person 类型没有 study() 方法
}
}
输出:
=== 学生信息 ===
我是 张三,今年 20 岁。
我是学生,学号 S001,专业 计算机科学
张三 正在学习 计算机科学
=== 教师信息 ===
我是 李老师,今年 35 岁。
我是老师,工号 T001,职称 教授
李老师 正在授课
=== 多态演示 ===
我是 王五,今年 22 岁。
我是学生,学号 S002,专业 数学
10. 今天的总结
今天我们学习了面向对象的第二大特性 —— 继承:
- 继承:子类通过
extends获得父类的属性和方法,实现代码复用。 super:访问父类的构造方法(super(...))或父类的成员(super.xxx)。- 方法重写(Override):子类重新定义父类的方法,实现个性化行为。记得加
@Override。 - 构造调用链:从父类到子类依次构造,层层递进。
- 单继承:一个子类只能有一个直接父类,避免菱形继承问题。
继承让代码能够复用,让类与类之间形成层次结构,是面向对象中组织和扩展代码的关键工具。
动手试试
-
定义一个
Vehicle(交通工具)类,包含brand、speed属性和move()方法。再定义两个子类Car和Bicycle,Car增加engineType属性和honk()方法,Bicycle增加hasBell属性和ringBell()方法。各自重写move()方法,输出不同的移动方式。 -
在上题的基础上,写一个方法
testDrive(Vehicle v),接收Vehicle参数,调用move()方法。传入Car和Bicycle对象,观察多态的效果。 -
定义一个
Shape(图形)父类,包含getArea()方法(返回 0)。定义Circle、Rectangle、Triangle子类,各自实现getArea()的计算方法。在main中创建一个Shape数组,放入不同图形,循环调用getArea(),打印面积。 -
(思考)如果你在
Person类中把name设为private,在Student中还能直接访问name吗?如果不能,Student如何获取或修改自己的名字?结合super和 getter/setter 来回答。
继承的知识很多,但核心就一条:“子类是一种父类”。牢牢抓住这个“is-a”关系,遇到任何继承场景,先问自己:“这两个类之间是 is-a 关系吗?”如果不是,就不要用继承。
我们下一篇会讲多态——在继承的基础上,让同一个方法表现出不同的行为。它是面向对象的第三大特性,也是让代码真正“活”起来的关键。
我们下一篇见。😃
📌 获取本系列示例代码请访问 GitCode。

1万+

被折叠的 条评论
为什么被折叠?



