Skip to content

继承

概述

继承(Inheritance)是面向对象三大特性之一,指子类(Subclass)可以拥有父类(Superclass)的成员变量和方法,从而形成「is-a」关系并实现代码复用。在 Java 中通过关键字 extends 声明继承,子类自动获得父类非 private 的成员,并可重写(override)父类方法以改变行为。Java 只支持单继承(一个类只能继承一个父类),通过 接口 弥补多继承能力。

掌握继承是理解 多态抽象类接口 的基础。

前置建议

已掌握 类与对象构造方法封装与访问修饰符,理解 protected 在子类中的可见性。


为什么需要继承

  • 代码复用:公共的字段和方法写在父类,子类直接使用,避免重复。
  • 层次建模:用「狗是一种动物」这样的 is-a 关系组织类型,更符合现实世界。
  • 多态基础:父类引用可指向子类对象,便于统一处理多种子类型(见 多态)。

基本语法

声明继承

java
// 父类
public class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public void eat() { System.out.println(name + " is eating."); }
}

// 子类通过 extends 继承父类
public class Dog extends Animal {
    public Dog(String name) {
        super(name);  // 必须调用父类构造方法
    }
}
  • extends 父类名:表示当前类继承自该父类。
  • super:在子类中用于调用父类的构造方法或成员(见下文)。

单继承规则

Java 中一个类只能继承一个直接父类,不能写 class A extends B, C。若需要多种能力,应使用 接口(一个类可实现多个接口)。


super 关键字

super 在子类中表示「父类」,主要有两种用法。

1. 调用父类构造方法

子类构造方法中,必须先调用父类构造方法(显式写 super(...) 或由编译器插入无参 super()),且必须出现在构造方法体的第一行

java
public class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name);   // 必须第一行,且必须调用
        this.breed = breed;
    }
}

若父类只有带参构造方法,子类必须显式写出 super(参数);若父类有无参构造方法,子类不写时编译器会自动插入 super()

注意

若父类没有无参构造方法,子类必须显式调用 super(参数),否则编译报错。

2. 访问父类成员

当子类重写了父类方法或隐藏了父类字段时,可用 super.方法名()super.字段名 访问父类版本。

java
public class Animal {
    protected String name = "Animal";
    public void speak() {
        System.out.println("Animal speaks");
    }
}

public class Dog extends Animal {
    private String name = "Dog";  // 隐藏了父类 name(不推荐这样写)

    @Override
    public void speak() {
        super.speak();           // 调用父类的 speak
        System.out.println("Dog barks: Wang!");
    }
}

方法重写(Override)

子类可以重新实现父类中已有的实例方法,称为方法重写。重写后,通过子类对象调用该方法时,执行的是子类的实现。

重写规则

  • 方法签名一致:方法名、参数列表必须与父类相同。
  • 返回类型:与父类相同或是其子类型(协变返回类型)。
  • 访问权限:不能比父类更严格(如父类 protected,子类不能改为 private)。
  • 不能重写staticfinalprivate 方法不可被重写。

建议在子类重写方法上加上 @Override 注解,让编译器帮你检查是否真的构成了重写(否则容易变成「重载」或拼写错误)。

java
public class Animal {
    public String getSound() {
        return "???";
    }
}

public class Dog extends Animal {
    @Override
    public String getSound() {
        return "Wang!";
    }
}

// 使用
Animal a = new Dog();
System.out.println(a.getSound());  // 输出 Wang!(多态:实际执行子类方法)

使用示例

示例 1:基本继承与构造链

子类构造时,会先执行父类构造方法(再往上直到 Object),再执行子类构造方法体,形成构造链

java
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
        System.out.println("Animal(String)");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
        System.out.println("Dog(String)");
    }
}

// 执行 new Dog("Lucky") 时输出顺序为:
// Animal(String)
// Dog(String)

示例 2:重写与 super 调用

子类重写父类方法,并在其中用 super 复用父类逻辑,再扩展自己的逻辑。

java
public class Employee {
    private String name;
    private double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public String getName() { return name; }
    public double getBaseSalary() { return baseSalary; }

    /** 计算月薪,子类可重写 */
    public double getMonthlyPay() {
        return baseSalary;
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', baseSalary=" + baseSalary + "}";
    }
}

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double baseSalary, double bonus) {
        super(name, baseSalary);
        this.bonus = bonus;
    }

    @Override
    public double getMonthlyPay() {
        return super.getMonthlyPay() + bonus;  // 复用父类逻辑并加上奖金
    }

    @Override
    public String toString() {
        return "Manager{" + super.toString() + ", bonus=" + bonus + "}";
    }
}

// 使用
Manager m = new Manager("张三", 10000, 2000);
System.out.println(m.getMonthlyPay());  // 12000.0

示例 3:继承层次与 is-a 关系

用继承表达「猫、狗都是动物」的层次,公共行为放在 Animal,子类只写各自特有行为。

java
public class Animal {
    protected String name;

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

    public void eat() {
        System.out.println(name + " is eating.");
    }
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }

    public void bark() {
        System.out.println(name + " barks: Wang!");
    }
}

public class Cat extends Animal {
    public Cat(String name) { super(name); }

    public void meow() {
        System.out.println(name + " says: Miao!");
    }
}

// 使用:父类引用可指向子类对象(多态的基础)
Animal a1 = new Dog("Lucky");
Animal a2 = new Cat("Mimi");
a1.eat();  // Lucky is eating.
a2.eat();  // Mimi is eating.
((Dog) a1).bark();  // 需要强转为 Dog 才能调用 bark()

继承中的成员访问

  • 父类 private 成员:子类不能直接访问,只能通过父类提供的 public/protected 方法间接使用。
  • 父类 protected / public 成员:子类可直接访问(protected 常用于设计给子类用的字段或方法)。
  • 同名成员变量:子类声明与父类同名的字段会隐藏父类字段(不推荐),用 super.字段名 可访问父类版本。更推荐用 getter/setter 或重写方法,而不是隐藏字段。

提示

优先用方法在父类中暴露行为,子类通过重写方法改变行为;避免在子类中声明与父类同名的成员变量造成隐藏,降低可读性。


注意事项

  • 单继承:一个类只能 extends 一个类;多能力组合用 接口 实现。
  • 构造链:子类构造方法必须先调用 super(...)(或依赖编译器插入 super()),且必须位于第一行;不能先写 this(...) 再写 super(...),二者只能其一且在第一行。
  • 重写与重载:重写是子类与父类相同签名的方法;重载是同类中不同参数列表的多个方法,二者不要混淆。
  • Object 是所有类的根:未写 extends 的类默认继承 Object,因此所有对象都有 equalshashCodetoString 等方法,可重写以符合业务语义。

注意

重写 equals 时务必同时重写 hashCode,否则在基于哈希的集合(如 HashMapHashSet)中行为异常。详见 常用类


相关链接

基于 VitePress 构建