继承与 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 {
}

AnimalDog 的:

直接父类

如果继续:

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 知识问答

  1. 什么是继承?
  2. Java 使用什么关键字建立继承关系?
  3. 什么是父类?
  4. 什么是子类?
  5. class B extends A 中谁是父类,谁是子类?
  6. 为什么要使用继承?
  7. 继承如何减少重复代码?
  8. 什么是 is-a 关系?
  9. 为什么 Dog extends Animal 比较合理?
  10. 为什么 Car extends Engine 通常不合理?
  11. 子类可以继续定义自己的成员吗?
  12. private 成员应该如何理解?
  13. 构造器能不能被继承?
  14. Java 类支持多继承吗?
  15. 什么是单继承?
  16. 什么是多层继承?
  17. 一个父类可以拥有多个子类吗?
  18. Java 类最终与 Object 存在什么关系?
  19. 为什么继承不能只被理解成减少代码?
  20. 继承为什么是后续多态的重要基础?

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();
    }
}

不运行程序,回答:

  1. Teacher 的直接父类是谁?
  2. Teacher 自己声明了哪些方法?
  3. eat() 来自哪里?
  4. teacher 能否调用 eat()
  5. People 对象能否直接调用 teach()?为什么?

阅读:

class A {
}

class B extends A {
}

class C extends B {
}

回答:

  1. B 的直接父类是谁?
  2. C 的直接父类是谁?
  3. A 是否是 C 继承链中的上层类型?
  4. 这是不是多继承?
  5. 这属于什么继承结构?

7.3 手写代码

任务一:People / Teacher / Consultant

设计:

People
Teacher
Consultant

已知:

Teacher:
name
sex
skill

Consultant:
name
sex
customerCount

要求:

  1. 找出公共成员。
  2. 抽取合理父类。
  3. 使用 extends 建立继承。
  4. 父类字段保持合理封装。
  5. 子类保留自身特有属性。
  6. 编写测试类验证。

任务二: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

回答:

  1. 当前继承表达的语义是什么?
  2. Car is an Engine 是否合理?
  3. 这里是否只是为了复用代码而错误使用了继承?
  4. 更合理的对象关系应该如何描述?

分析:

class A {
}

class B {
}

class C extends A, B {
}

回答:

  1. Java 是否允许?
  2. 问题属于语法错误还是运行时错误?
  3. Java 类采用什么继承模式?
  4. 如果需要表达多种能力,后续 Java 还提供什么重要机制?先只写出知识名称,不要求实现。

7.5 综合训练

某公司存在:

经理 Manager
程序员 Programmer
测试工程师 Tester

共同信息:

id
name
age
salary

经理独有:

bonus
manage()

程序员独有:

language
writeCode()

测试工程师独有:

testTool
testSoftware()

要求:

  1. 分析应该抽取什么父类。
  2. 画出继承体系。
  3. 编写父类。
  4. 编写三个子类。
  5. 使用 extends 建立关系。
  6. 每个子类保留自己的独有成员。
  7. 创建三个对象进行验证。
  8. 不使用方法重写、多态、抽象类或接口。

完成后回答:

你设计继承关系时依据的是“减少重复代码”,还是“is-a 类型关系”?两者之间是什么关系?


7.6 本章验收

不查看资料,检查自己是否能够:

  • [ ] 30 秒解释继承是什么。
  • [ ] 手写 class B extends A
  • [ ] 正确区分父类与子类。
  • [ ] 从两个重复类中抽取公共父类。
  • [ ] 解释继承为什么提高代码复用性。
  • [ ] 解释什么是 is-a。
  • [ ] 判断一个继承设计是否合理。
  • [ ] 解释 private 父类状态为什么不能被子类直接访问。
  • [ ] 说明构造器不能被继承。
  • [ ] 解释 Java 类为什么叫单继承。
  • [ ] 写出一个三层继承体系。
  • [ ] 解释一个父类为什么可以有多个子类。
  • [ ] 画出 People → Teacher / Consultant 继承图。
  • [ ] 理解 Java 普通类最终处于 Object 继承体系中。

如果你只会写:

extends

但看到真实需求时无法判断:

哪些类应该存在父子关系、哪些公共成员应该向上抽取,

那么这一章还没有真正掌握。