继承与 Java 类继承体系 | JavaSE
继承与 Java 类继承体系
一、学习目标
学完本章,你应该能够:
- 能够解释什么是继承(Inheritance),理解父类与子类的关系。
- 能够使用
extends建立 Java 类之间的继承关系。 - 能够从多个相似类中识别公共数据和公共行为,并抽取父类。
- 能够解释继承为什么能够减少重复代码、提高复用性。
- 能够理解 Java 类的单继承和多层继承结构。
- 能够解释“一个子类只有一个直接父类”的含义。
- 能够区分继承关系与普通对象包含关系,避免为了复用代码滥用继承。
- 能够理解父类 private 状态与“子类不能直接访问 private 成员”之间的区别。
- 能够画出简单的 Java 类继承树。
二、核心知识
2.1 从重复代码开始
假设需要设计两个类:
Teacher
Consultant
讲师拥有:
姓名
性别
技能
咨询师拥有:
姓名
性别
接待人数
如果完全独立设计:
public class Teacher {
private String name;
private char sex;
private String skill;
// getter / setter ...
}
public class Consultant {
private String name;
private char sex;
private int number;
// getter / setter ...
}
可以发现:
name
sex
以及对应访问代码高度重复。
两个类共同表达的其实都是:
人。
于是可以抽取:
People
作为公共父类。
2.2 什么是继承
继承(Inheritance)是 Java 面向对象的重要机制。
Java 使用:
extends
建立类与类之间的继承关系。
基本语法:
class 子类 extends 父类 {
}
例如:
public class Teacher extends People {
}
可以读成:
Teacher继承People。
此时:
People
称为:
- 父类(Superclass)
- 超类(Superclass)
- 基类(Base Class)
而:
Teacher
称为:
- 子类(Subclass)
- 派生类(Derived Class)
Java 官方术语中最常见的是:
superclass
subclass
2.3 继承表达什么关系
继承不只是:
为了少写几行代码。
它首先表达的是一种类型关系:
Teacher 是一种 People
Consultant 是一种 People
Dog 是一种 Animal
Cat 是一种 Animal
可以简单理解成:
is-a 关系。
例如:
class Animal {
}
class Dog extends Animal {
}
表示:
Dog is an Animal
也就是:
狗是一种动物。
这是判断继承是否合理的重要依据。
2.4 为什么需要继承
继承最直接的价值之一是:
抽取公共代码,提高复用性。
原来:
Teacher
├── name
├── sex
└── skill
Consultant
├── name
├── sex
└── number
其中:
name
sex
重复。
抽取后:
People
├── name
└── sex
↑
│
├── Teacher
│ └── skill
│
└── Consultant
└── number
共同数据统一放在:
People
子类只保留自己的特征。
这样可以减少:
- 重复成员;
- 重复 getter / setter;
- 重复公共行为;
- 后续维护时的重复修改。
2.5 第一个继承案例
父类:
public class People {
private String name;
private char sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
讲师:
public class Teacher extends People {
private String skill;
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
咨询师:
public class Consultant extends People {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
测试:
public class Test {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.setName("张老师");
teacher.setSex('男');
teacher.setSkill("Java");
System.out.println(teacher.getName());
System.out.println(teacher.getSex());
System.out.println(teacher.getSkill());
}
}
虽然:
Teacher
自己没有重新定义:
getName()
setName()
getSex()
setSex()
但它可以使用从父类继承而来的可访问方法。
2.6 子类拥有自己的独有成员
继承并不意味着:
子类只能拥有父类的东西。
子类可以继续增加自己的数据和行为。
例如:
public class Teacher extends People {
private String skill;
public void teach() {
System.out.println("正在教授:" + skill);
}
}
于是:
Teacher
既具备:
People 的公共特征
又具备:
Teacher 自己的独有特征
可以建立模型:
父类
提供公共部分
↓
子类
继承公共部分
+
扩展自己的部分
2.7 一个非常重要的问题:private 成员到底继不继承
原始入门资料通常会简化为:
子类继承父类的非私有成员。
这种说法适合作为“能不能直接访问”的入门提示,但如果严格理解,会产生误区。
看:
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
子类:
public class Teacher extends People {
}
Teacher 中不能直接:
name = "张三";
原因是:
name 被 private 限制
子类代码没有直接访问权限。
但是,当创建:
Teacher teacher = new Teacher();
这个对象仍然需要保存 People 所定义的父类状态。
所以更准确的理解应该是:
private 成员属于父类定义的内部状态,但子类代码不能直接访问它。
因此通常需要通过父类公开或受保护的行为访问,例如:
teacher.setName("张三");
至于:
private
缺省
protected
public
完整权限规则将在下一章专门学习。
2.8 构造器不会被继承
虽然子类能够继承父类的很多成员,但是:
构造器不属于可继承成员
例如:
public class People {
public People(String name) {
}
}
并不意味着子类自动拥有:
public Teacher(String name) {
}
子类需要定义自己的构造器。
创建子类对象时父子类构造器之间如何协作,是后续:
02-09 子类构造器与 this(...) / super(...)
专门解决的问题。
本章先记住:
构造器不能被继承。
2.9 Java 类是单继承
Java 的类采用:
单继承(Single Inheritance)
一个类只能有:
一个直接父类
合法:
class A {
}
class B extends A {
}
不允许:
class C extends A, B {
}
也就是说 Java 类不能同时写:
两个直接父类
2.10 什么叫“直接父类”
例如:
class Animal {
}
class Dog extends Animal {
}
Animal 是 Dog 的:
直接父类
如果继续:
class Husky extends Dog {
}
那么:
Dog
是 Husky 的直接父类。
而:
Animal
属于它继承链上的更上层父类。
2.11 Java 支持多层继承
虽然 Java 类不支持:
一个类同时直接继承多个类
但支持:
多层继承(Multilevel Inheritance)
例如:
class Animal {
}
class Dog extends Animal {
}
class Husky extends Dog {
}
形成:
Animal
↑
Dog
↑
Husky
因此:
Husky
处于一条继承链中。
它直接继承:
Dog
而 Dog 又继承:
Animal
2.12 单继承不等于只能有一个子类
这个概念特别容易混淆。
Java:
一个子类只能有一个直接父类
但是一个父类可以拥有很多子类。
例如:
Animal
/ \
Dog Cat
代码:
class Dog extends Animal {
}
class Cat extends Animal {
}
完全合法。
所以:
单继承
限制的是:
子类直接父类的数量。
不是:
父类只能有一个子类。
2.13 为什么 Java 类不支持直接多继承
从语言规则上来说:
Java 类就是不允许一个类同时声明多个直接父类。
教学中常使用下面的冲突案例帮助理解:
class FatherA {
public void study() {
System.out.println("学习数学");
}
}
class FatherB {
public void study() {
System.out.println("学习语文");
}
}
如果假设允许:
class Child extends FatherA, FatherB {
}
那么调用:
child.study();
就可能需要额外解决:
到底使用 FatherA.study()
还是 FatherB.study()
Java 选择让:
类继承
保持单继承结构,从语言设计层面避免形成这种直接多父类模型。
需要注意:
“存在同名方法冲突”是理解单继承的一种典型教学示例,不应该反过来理解成“Java 设计单继承的唯一原因”。
2.14 Java 类继承体系
如果一个普通类没有显式写出父类,例如:
public class Student {
}
它仍然处于 Java 的类继承体系中。
Java 普通类最终都位于:
Object
这一根类型之下。
可以先建立简单模型:
Object
↑
People
↑
Teacher
或者:
Object
↑
Animal
┌─┴─┐
Dog Cat
关于:
Object
本身提供了哪些成员、为什么称它为“祖宗类”,将在下一章专门学习。
本章只要求建立:
Java 类天然处在继承体系中。
三、使用方法
3.1 从重复类中抽取父类
假设首先出现:
public class Teacher {
private String name;
private int age;
private String skill;
}
以及:
public class Consultant {
private String name;
private int age;
private int customerCount;
}
分析:
Teacher:
name
age
skill
Consultant:
name
age
customerCount
共同部分:
name
age
可以抽取:
public class Employee {
private String name;
private int age;
// getter / setter
}
再:
public class Teacher extends Employee {
private String skill;
}
public class Consultant extends Employee {
private int customerCount;
}
完成过程可以总结为:
寻找多个类
↓
识别重复属性 / 行为
↓
判断它们是否存在真正共同父类型
↓
抽取父类
↓
子类 extends 父类
↓
子类保留自身独有部分
3.2 父类不仅可以抽取数据
除了公共字段,公共行为也可以放入父类。
例如:
public class Animal {
public void breathe() {
System.out.println("呼吸");
}
}
public class Dog extends Animal {
public void lookHome() {
System.out.println("看家");
}
}
public class Cat extends Animal {
public void catchMouse() {
System.out.println("抓老鼠");
}
}
于是:
Dog dog = new Dog();
dog.breathe();
dog.lookHome();
其中:
breathe()
属于公共行为。
而:
lookHome()
属于 Dog 自己的行为。
3.3 如何判断某个成员应该放在哪一层
例如:
Animal
Dog
Cat
分析:
name
age
eat
如果所有动物都有,可以考虑放:
Animal
而:
lookHome
只属于狗。
放:
Dog
而:
catchMouse
只属于猫。
放:
Cat
原则:
公共特征尽量放公共父类,特有特征保留在具体子类。
但必须注意:
只有真正属于父子类型关系的概念才适合使用继承。
四、原理与进阶
4.1 继承不是简单复制代码
不要把:
class Teacher extends People {
}
理解成编译器把:
People.java
里的源代码复制粘贴到:
Teacher.java
里面。
继承表达的是:
类型之间建立了父子关系,子类可以依据 Java 继承与访问规则获得父类定义的状态和行为能力。
它是语言层面的类型机制,不是文本复制工具。
4.2 继承首先是类型建模,其次才是代码复用
如果只为了少写代码就使用继承,可能产生错误模型。
假设:
Car
Engine
汽车内部有发动机。
能不能写:
class Car extends Engine {
}
从代码复用角度可能“似乎能用”。
但是语义变成:
Car is an Engine
也就是:
汽车是一种发动机。
显然不合理。
更正确的关系是:
Car has an Engine
汽车“拥有”发动机,而不是“属于”发动机。
所以设计继承时应该先问:
子类真的是父类的一种吗?
而不是只问:
这里有没有几行代码可以复用?
4.3 继承层次不能无限追求深
虽然 Java 支持:
A
↑
B
↑
C
↑
D
↑
E
这种多层继承。
但继承层次过深会让代码理解变困难。
你查看:
someObject.method();
可能需要不断向上寻找:
当前类?
父类?
祖父类?
更上层?
因此工程设计中通常应该保持:
类型层次清晰,而不是为了“显得面向对象”制造大量父类。
4.4 父类应该表达稳定的公共抽象
例如:
Teacher
Consultant
抽取:
People
的前提是:
它们真的都属于 People
并且:
name
sex
属于这个公共抽象。
好的父类不是:
把重复代码随便塞进去。
而应该表示:
多个子类之间稳定、合理的共同概念。
4.5 private 与封装仍然有效
继承并不会破坏上一章学习的封装。
例如父类:
public class People {
private String name;
public String getName() {
return name;
}
}
子类不能因为:
extends People
就绕过:
private
直接操作:
name
这说明:
继承和封装不是相互对立的。
父类仍然能够控制自己的内部状态如何被子类或其他代码访问。
权限修饰符的完整机制下一章继续学习。
五、实践应用
5.1 员工体系
可以设计:
Employee
├── Manager
├── Programmer
├── Tester
└── Designer
公共属性:
id
name
salary
可以放:
Employee
而不同岗位自己拥有不同功能。
例如:
public class Employee {
private String id;
private String name;
private double salary;
// getter / setter
}
public class Programmer extends Employee {
private String programmingLanguage;
public void writeCode() {
System.out.println("编写代码");
}
}
这种结构可以明显表达:
Programmer is an Employee
5.2 动物体系
Animal
├── Dog
├── Cat
└── Bird
共同属性:
name
age
共同能力:
eat
sleep
可以放入:
Animal
子类继续扩展:
Dog → lookHome
Cat → catchMouse
Bird → fly
后续学习:
方法重写
多态
抽象类
接口
以后,这棵继承树会逐渐从:
单纯代码复用
发展为:
真正的面向对象类型体系。
5.3 继承是后续多态的基础
当前先理解:
class Dog extends Animal {
}
表示:
Dog 是 Animal 的子类型
后面学习多态以后,会进一步使用:
Animal animal = new Dog();
这就是为什么继承不是一个孤立语法知识。
它会继续连接:
继承
↓
方法重写
↓
多态
↓
抽象类
↓
接口与面向抽象编程
本章暂时不展开这些后续机制。
六、常见问题
6.1 继承使用什么关键字?
extends
例如:
class Dog extends Animal {
}
6.2 extends 后面是谁?
class Dog extends Animal {
}
其中:
Dog
是子类。
Animal
是直接父类。
6.3 继承最大的作用是什么?
课程入门阶段主要掌握:
- 抽取公共代码;
- 提高代码复用;
- 减少重复;
- 建立父子类型关系;
- 为后续方法重写和多态建立基础。
6.4 子类能不能继续定义自己的成员?
当然可以。
继承只是获得父类公共能力,并不限制子类增加自己的特征。
6.5 private 字段是不是“子类对象里面完全不存在”?
不能这样理解。
更准确地说:
父类 private 状态仍然由父类部分维护,但子类代码不能直接访问。
访问规则下一章进一步学习。
6.6 构造器能不能继承?
不能。
子类构造器与父类构造器的调用关系将在后续专章学习。
6.7 Java 一个类可以直接继承两个类吗?
不可以。
不能:
class C extends A, B {
}
Java 类采用单继承。
6.8 Java 支持多层继承吗?
支持。
例如:
class A {
}
class B extends A {
}
class C extends B {
}
6.9 一个父类能有多个子类吗?
可以。
例如:
Animal
├── Dog
├── Cat
└── Bird
单继承限制的是:
一个子类只能有一个直接父类。
6.10 所有重复代码都应该抽成父类吗?
不是。
必须先判断两个概念之间是否存在合理的:
is-a
关系。
如果只是:
A 拥有 B
则通常不是继承。
6.11 继承层次是不是越深越专业?
不是。
过深继承层次会增加理解和维护成本。
继承结构应该:
合理
清晰
稳定
而不是越复杂越好。
6.12 Object 是什么?
可以先理解为:
Java 普通类继承体系的根类型。
关于 Object 的具体规则和常用方法将在下一章:
权限修饰符与 Object 祖宗类
继续学习。
6.13 super 为什么这章没有详细讲?
super 会涉及:
- 父类成员访问;
- 方法重写;
- 父类构造器调用。
冻结课程已经分别安排到后续章节,因此本章不提前完整展开。
七、练习与验收
7.1 知识问答
- 什么是继承?
- Java 使用什么关键字建立继承关系?
- 什么是父类?
- 什么是子类?
class B extends A中谁是父类,谁是子类?- 为什么要使用继承?
- 继承如何减少重复代码?
- 什么是
is-a关系? - 为什么
Dog extends Animal比较合理? - 为什么
Car extends Engine通常不合理? - 子类可以继续定义自己的成员吗?
- private 成员应该如何理解?
- 构造器能不能被继承?
- Java 类支持多继承吗?
- 什么是单继承?
- 什么是多层继承?
- 一个父类可以拥有多个子类吗?
- Java 类最终与 Object 存在什么关系?
- 为什么继承不能只被理解成减少代码?
- 继承为什么是后续多态的重要基础?
7.2 代码阅读
阅读:
class People {
public void eat() {
System.out.println("eat");
}
}
class Teacher extends People {
public void teach() {
System.out.println("teach");
}
}
public class Test {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.eat();
teacher.teach();
}
}
不运行程序,回答:
Teacher的直接父类是谁?Teacher自己声明了哪些方法?eat()来自哪里?teacher能否调用eat()?People对象能否直接调用teach()?为什么?
阅读:
class A {
}
class B extends A {
}
class C extends B {
}
回答:
B的直接父类是谁?C的直接父类是谁?A是否是C继承链中的上层类型?- 这是不是多继承?
- 这属于什么继承结构?
7.3 手写代码
任务一:People / Teacher / Consultant
设计:
People
Teacher
Consultant
已知:
Teacher:
name
sex
skill
Consultant:
name
sex
customerCount
要求:
- 找出公共成员。
- 抽取合理父类。
- 使用
extends建立继承。 - 父类字段保持合理封装。
- 子类保留自身特有属性。
- 编写测试类验证。
任务二:Animal 继承体系
设计:
Animal
Dog
Cat
要求:
Animal:
name
age
eat()
Dog:
lookHome()
Cat:
catchMouse()
暂时不使用:
方法重写
多态
抽象类
只练习:
父类抽取
extends
公共成员
子类特有成员
任务三:Employee
根据:
Manager
Programmer
Tester
自行分析:
- 哪些数据应该进入
Employee; - 哪些数据应该留在子类;
- 为什么
Programmer is an Employee成立。
最后画出继承结构图。
7.4 Debug
下面代码存在设计问题:
class Car extends Engine {
}
已知业务语义:
Car 拥有 Engine
回答:
- 当前继承表达的语义是什么?
Car is an Engine是否合理?- 这里是否只是为了复用代码而错误使用了继承?
- 更合理的对象关系应该如何描述?
分析:
class A {
}
class B {
}
class C extends A, B {
}
回答:
- Java 是否允许?
- 问题属于语法错误还是运行时错误?
- Java 类采用什么继承模式?
- 如果需要表达多种能力,后续 Java 还提供什么重要机制?先只写出知识名称,不要求实现。
7.5 综合训练
某公司存在:
经理 Manager
程序员 Programmer
测试工程师 Tester
共同信息:
id
name
age
salary
经理独有:
bonus
manage()
程序员独有:
language
writeCode()
测试工程师独有:
testTool
testSoftware()
要求:
- 分析应该抽取什么父类。
- 画出继承体系。
- 编写父类。
- 编写三个子类。
- 使用
extends建立关系。 - 每个子类保留自己的独有成员。
- 创建三个对象进行验证。
- 不使用方法重写、多态、抽象类或接口。
完成后回答:
你设计继承关系时依据的是“减少重复代码”,还是“is-a 类型关系”?两者之间是什么关系?
7.6 本章验收
不查看资料,检查自己是否能够:
- [ ] 30 秒解释继承是什么。
- [ ] 手写
class B extends A。 - [ ] 正确区分父类与子类。
- [ ] 从两个重复类中抽取公共父类。
- [ ] 解释继承为什么提高代码复用性。
- [ ] 解释什么是 is-a。
- [ ] 判断一个继承设计是否合理。
- [ ] 解释 private 父类状态为什么不能被子类直接访问。
- [ ] 说明构造器不能被继承。
- [ ] 解释 Java 类为什么叫单继承。
- [ ] 写出一个三层继承体系。
- [ ] 解释一个父类为什么可以有多个子类。
- [ ] 画出 People → Teacher / Consultant 继承图。
- [ ] 理解 Java 普通类最终处于 Object 继承体系中。
如果你只会写:
extends
但看到真实需求时无法判断:
哪些类应该存在父子关系、哪些公共成员应该向上抽取,
那么这一章还没有真正掌握。