Skip to content

S02-03 面向对象-继承

[TOC]

继承

概述

多个类存在相同的属性和方法时,代码冗余,维护成本高。通过继承抽取父类,实现代码复用。

Pupil(小学生)和 Graduate(大学生)都有 name、age、score 属性和 testing()、showInfo()方法,可抽取父类 Student。

继承(Inheritance):是基于已有的类(父类 / 超类)创建新类(子类 / 派生类),子类复用父类的属性和方法,同时可扩展自身的功能。它是实现代码复用、构建类层次结构、支撑多态的核心机制。

本质代码复用 + 类层次化

  • 代码复用:避免重复编写相同的属性和方法(如多个子类都需要 “姓名、年龄” 属性,可抽离到父类);
  • 类层次化:构建 “通用 → 具体” 的类结构(如 AnimalMammalDog),符合现实世界的分类逻辑;
  • 多态基础:继承是多态的前提(子类对象可赋值给父类引用)。

继承关系示意图

image-20260108154450527

基本语法

基本语法

使用 extends 关键字声明继承关系,格式如下:

java
// 父类(超类/基类)
public class 父类名 {
    // 属性、方法
}

// 子类(派生类):继承父类
public class 子类名 extends 父类名 {
    // 新增属性、方法,或重写父类方法
}

核心规则

  1. 规则 1:Java 是单继承

    子类只能直接继承一个父类(避免多继承的菱形问题),但可通过 “多层继承” 间接继承多个类的特性:

    java
    // 合法:多层继承
    class A {}
    class B extends A {}
    class C extends B {} // C 间接继承 A
    
    // 错误:多继承(Java 不支持)
    // class D extends A, B {}
  2. 规则 2:所有类默认继承 Object 类

    Java 中没有显式声明父类的类,默认继承 java.lang.Object 类(Object 是所有类的根父类)。

    Object 类提供了所有对象的通用方法(如 equals()hashCode()toString()),所有类都可直接使用或重写这些方法。

    java
    // 等价于 class Person extends Object {}
    public class Person {}
  3. 规则 3:访问权限决定继承可见性

    子类继承了父类的所有属性和方法,但 private 成员因访问权限限制无法直接访问

    java
    class Parent {
        public String pubVar = "公共变量";
        protected String proVar = "保护变量";
        String defVar = "默认变量";
        private String priVar = "私有变量"; // 子类无法继承
    }
    
    class Child extends Parent {
        public void show() {
            System.out.println(pubVar); // ✅ 合法:继承public成员
            System.out.println(proVar); // ✅ 合法:继承protected成员
            System.out.println(defVar); // ✅ 合法:同包下继承default成员
            // System.out.println(priVar); // ❌ 错误:无法访问private成员
        }
    }

    如果子类要访问父类的 private 成员,需要通过父类的 非私有方法 访问:

    java
    class Parent {
        private String priVar = "私有变量"; // 子类无法继承
    
        // 1. 通过非私有方法(public/protected/default)访问私有属性
        public String getPriVar() {
            return this.priVar;
        }
    }
    
    class Child extends Parent {
        public void show() {
            getPriVar() // 2. 子类访问
        }
    }

快速入门

java
// 父类:Student
public class Student {
    // 共有属性
    public String name;
    public int age;
    private double score; // 私有属性

    // 共有方法
    public void setScore(double score) {
        this.score = score;
    }

    public void showInfo() {
        System.out.println("学生名" + name + " 年龄" + age + " 成绩" + score);
    }
}
java
// 子类:Pupil(小学生)
public class Pupil extends Student {
    // 特有方法
    public void testing() {
        System.out.println("小学生" + name + " 正在考小学数学..");
    }
}
java
// 子类:Graduate(大学生)
public class Graduate extends Student {
    // 特有方法
    public void testing() {
        System.out.println("大学生" + name + " 正在考大学数学..");
    }
}

成员变量的继承

成员变量的继承规则

  1. 子类继承父类非私有变量

    子类可直接使用父类的 public/protected/default 成员变量:

    java
    class Animal {
        protected String name; // 父类保护变量
        protected int age;
    }
    
    class Dog extends Animal {
        public void setInfo(String name, int age) {
            this.name = name; // 直接使用父类的 name
            this.age = age;   // 直接使用父类的 age
        }
    }
  2. 子类同名变量隐藏父类变量

    子类声明与父类同名的变量时,会 “隐藏” 父类的变量(而非覆盖),可通过 super 关键字访问父类变量:

    java
    class Parent {
        String name = "父类姓名";
    }
    
    class Child extends Parent {
        String name = "子类姓名"; // 隐藏父类 name
    
        public void show() {
            System.out.println(name); // 子类姓名(就近原则)
            System.out.println(super.name); // 父类姓名(通过super访问)
        }
    }

方法的继承

方法的继承

子类可直接调用父类的非私有方法:

java
class Animal {
    public void eat() {
        System.out.println("动物进食");
    }
}

class Cat extends Animal {
    public void play() {
        this.eat(); // 调用继承的父类方法
        System.out.println("猫玩球");
    }
}

方法重写

概述

方法重写(Override):子类定义与父类 “方法签名完全一致” 的方法,覆盖父类的原有逻辑。

方法签名:方法名 + 参数列表(参数个数、类型、顺序),返回值需兼容(Java 5+ 支持协变返回)。

方法重写规则

方法重写的核心规则必须遵守,否则编译报错

  • 方法签名一致:方法名、参数列表(个数 / 类型 / 顺序)必须完全相同
  • 返回值兼容:子类返回值需是父类返回值的子类(或相同)(协变返回
  • 访问权限不降级:子类方法的访问权限不能比父类更严格(如父类 public → 子类不能 private)
  • 异常不扩大:子类方法抛出的异常不能比父类更广泛(如父类抛 RuntimeException → 子类不能抛 Exception)

示例:正确 vs 错误

java
// 父类
class Parent {
    public Object show(String msg) throws RuntimeException {
        System.out.println("父类方法:" + msg);
        return msg;
    }
}
java
// ✅ 子类:正确重写
class Child1 extends Parent {
    // 协变返回:Object → String(String是Object的子类)
    @Override // 注解:强制检查重写规则,推荐添加
    public String show(String msg) { // 父类抛RuntimeException,子类可不抛
        System.out.println("子类方法:" + msg);
        return msg;
    }
}
java
// ❌ 子类:错误重写(访问权限降级)
class Child2 extends Parent {
    // @Override // 编译报错:父类是public,子类是private
    private Object show(String msg) {
        return null;
    }
}

@Override

@Override 注解的作用

  • 强制编译器检查是否符合重写规则,避免拼写错误(如把 show 写成 shwo);
  • 增强代码可读性,明确标识该方法是重写父类的方法;
  • 推荐所有重写方法都添加该注解

方法重写 VS 方法重载

名称发生范围方法名形参列表返回类型修饰符
重载(overload)本类必须相同类型/个数/顺序至少一个不同无要求无要求
重写(override)父子类必须相同完全相同相同或子类子类不能缩小访问范围

练习

需求

  • Person 类:私有属性 name、age;构造器;say()方法返回自我介绍
  • Student 类:继承 Person,增加私有属性 id、score;构造器;重写say()方法
java
// 父类:Person
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String say() {
        return "name=" + name + " age=" + age;
    }

    // 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; }
}
java
// 子类:Student
public class Student extends Person {
    private int id;
    private double score;

    public Student(String name, int age, int id, double score) {
        super(name, age); // 调用父类构造器
        this.id = id;
        this.score = score;
    }

    // 重写say()方法
    @Override
    public String say() {
        // 复用父类的say()方法,添加子类属性
        return super.say() + " id=" + id + " score=" + score;
    }

    // getter/setter
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public double getScore() { return score; }
    public void setScore(double score) { this.score = score; }
}
java
// 测试类
public class OverrideExercise {
    public static void main(String[] args) {
        Person jack = new Person("jack", 10);
        System.out.println(jack.say()); // name=jack age=10

        Student smith = new Student("smith", 20, 123456, 99.8);
        System.out.println(smith.say()); // name=smith age=20 id=123456 score=99.8
    }
}

构造方法的继承

构造方法的继承

构造方法不能被继承(构造方法名必须与类名一致,子类类名不同),但子类构造方法可通过 super() 调用父类构造方法。

核心规则

  1. 子类构造方法默认调用父类的无参构造方法super()),且 super() 必须是构造方法的第一条语句

    java
    class Parent {
        public Parent() { // 无参构造器
            System.out.println("父类的无参构造器默认会被调用");
        }
    }
    
    class Child extends Parent {
        public Child() {
            // super() 子类构造方法默认调用父类的无参构造方法
        }
    }
  2. 若父类没有无参构造(仅含带参构造),子类构造方法必须显式调用父类的带参构造(super(参数));

    java
    // 父类:仅含带参构造(无默认无参构造)
    class Parent {
        private String name;
    
        public Parent(String name) { // 带参构造
            this.name = name;
        }
    }
    java
    // 子类:必须显式调用父类带参构造
    class Child extends Parent {
        private int age;
    
        // ✅ 正确:显式调用父类带参构造
        public Child(String name, int age) {
            super(name); // 必须是第一条语句
            this.age = age;
        }
    
        // ❌ 错误:未调用父类构造(父类无无参构造)
        // public Child() {} // 编译报错
    }
  3. 不能同时调用 super()this()(二者都需是第一条语句)。

super

概述

super访问父类成员

super 是继承场景的核心关键字,作用是在子类中访问父类的成员(变量 / 方法 / 构造),与 this 对应:

  • this 指向当前对象。
  • super 指向父类对象

核心用法

super 的核心用法

  • super.成员变量:访问父类的非私有成员变量(解决子类同名变量的隐藏问题)
  • super.方法名(参数):调用父类的非私有原方法(子类重写后,仍可调用父类原方法)
  • super(参数):调用父类的构造方法(必须是构造方法第一条语句)

示例:super 的使用

java
// 父类
class Parent {
    String name = "父类";

    public Parent(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println("父类show方法:" + name);
    }
}
java
// 子类
class Child extends Parent {
    String name = "子类";

    public Child(String parentName, String childName) {
        super(parentName); // 3. 调用父类带参构造
        this.name = childName;
    }

    @Override
    public void show() {
        super.show(); // 2. 调用父类的show方法
        System.out.println("子类show方法:" + this.name);
        System.out.println("父类name:" + super.name); // 1. 访问父类name
    }
}
java
// 测试
public class TestSuper {
    public static void main(String[] args) {
        Child child = new Child("父类姓名", "子类姓名");
        child.show();
        // 输出:
        // 父类show方法:父类姓名
        // 子类show方法:子类姓名
        // 父类name:父类姓名
    }
}

继承的访问修饰符

不同访问修饰符的成员在继承中的可见性,是继承的核心考点,以下是完整对比表:

父类成员修饰符同包子类可见不同包子类可见非子类可见子类能否继承
public
protected
default✅(同包)
private

关键说明

  • protected 是为继承设计的修饰符:不同包的子类可访问,但非子类不可访问;
  • default 仅同包的子类可继承,不同包的子类无法访问;
  • private 成员虽不能继承,但子类可通过父类的 public/protected 方法间接访问

继承的内存流程图

核心逻辑:子类对象创建后,会建立属性查找关系:

  1. 先查找子类是否有该属性,有则访问
  2. 若无,查找父类,有则访问(需满足访问权限)
  3. 若无,继续向上查找,直到 Object 类
  4. 注意:如果在查找的过程中遇到 private 属性,则不会在继续向上查找(即使上面有该属性并且可以访问),而是报错
java
// 爷类
class GrandPa {
    String name = "大头爷爷";
    String hobby = "旅游";
}

// 父类
class Father extends GrandPa {
    String name = "大头爸爸";
    private int age = 39; // 私有属性

    public int getAge() {
        return age;
    }
}

// 子类
class Son extends Father {
    String name = "大头儿子";
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println(son.name); // 大头儿子(子类有name属性)
        System.out.println(son.hobby); // 旅游(子类无,父类无,爷类有)
        System.out.println(son.getAge()); // 39(父类私有属性,通过公共方法访问)
    }
}

内存流程图

练习

  • 练习 1

    java
    class A {
        A() { System.out.println("a"); }
        A(String name) { System.out.println("a name"); }
    }
    
    class B extends A {
        B() { this("abc"); System.out.println("b"); }
        B(String name) { System.out.println("b name"); } // 有一个默认的 super();
    }
    
    // main中:B b = new B(); 输出什么?
    // 输出结果:a → b name → b
  • 练习 2

    java
    class A {
        public A() {
            System.out.println("我是A类");
        }
    }
    
    class B extends A {
        public B() {
            System.out.println("我是B类的无参构造");
        }
    
        public B(String name) {
            System.out.println(name + "我是B类的有参构造");
        }
    }
    
    class C extends B {
        public C() {
            this("hello");
            System.out.println("我是c类的无参构造");
        }
    
        public C(String name) {
            super("hahah");
            System.out.println("我是c类的有参构造");
        }
    }
    
    public class ExtendsExercise02 {
        public static void main(String[] args) {
            C c = new C(); // 输出:我是A类 → hahah我是B类的有参构造 → 我是c类的有参构造 → 我是c类的无参构造
        }
    }
  • 练习 3

    需求:

    • 编写 Computer 类:属性 CPU、内存、硬盘;方法getDetails()返回详细信息
    • 编写 PC 子类:继承 Computer,添加特有属性 brand(品牌)
    • 编写 NotePad 子类:继承 Computer,添加特有属性 color(颜色)
    • 测试类:创建 PC 和 NotePad 对象,赋值并打印信息
    java
    // 父类:Computer
    public class Computer {
        private String cpu;
        private int memory;
        private int disk;
    
        public Computer(String cpu, int memory, int disk) {
            this.cpu = cpu;
            this.memory = memory;
            this.disk = disk;
        }
    
        public String getDetails() {
            return "cpu=" + cpu + " memory=" + memory + " disk=" + disk;
        }
    
        // getter/setter
        public String getCpu() { return cpu; }
        public void setCpu(String cpu) { this.cpu = cpu; }
        public int getMemory() { return memory; }
        public void setMemory(int memory) { this.memory = memory; }
        public int getDisk() { return disk; }
        public void setDisk(int disk) { this.disk = disk; }
    }
    
    // 子类:PC
    public class PC extends Computer {
        private String brand;
    
        public PC(String cpu, int memory, int disk, String brand) {
            super(cpu, memory, disk); // 调用父类构造器
            this.brand = brand;
        }
    
        public void printInfo() {
            System.out.println("PC信息=" + getDetails() + " brand=" + brand);
        }
    
        // getter/setter
        public String getBrand() { return brand; }
        public void setBrand(String brand) { this.brand = brand; }
    }
    
    // 测试类
    public class ExtendsExercise03 {
        public static void main(String[] args) {
            PC pc = new PC("intel", 16, 500, "IBM");
            pc.printInfo(); // PC信息=cpu=intel memory=16 disk=500 brand=IBM
        }
    }