继承
概述
继承(Inheritance)是面向对象三大特性之一,指子类(Subclass)可以拥有父类(Superclass)的成员变量和方法,从而形成「is-a」关系并实现代码复用。在 Java 中通过关键字 extends 声明继承,子类自动获得父类非 private 的成员,并可重写(override)父类方法以改变行为。Java 只支持单继承(一个类只能继承一个父类),通过 接口 弥补多继承能力。
为什么需要继承
- 代码复用:公共的字段和方法写在父类,子类直接使用,避免重复。
- 层次建模:用「狗是一种动物」这样的 is-a 关系组织类型,更符合现实世界。
- 多态基础:父类引用可指向子类对象,便于统一处理多种子类型(见 多态)。
基本语法
声明继承
// 父类
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()),且必须出现在构造方法体的第一行。
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.字段名 访问父类版本。
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)。 - 不能重写:
static、final、private方法不可被重写。
建议在子类重写方法上加上 @Override 注解,让编译器帮你检查是否真的构成了重写(否则容易变成「重载」或拼写错误)。
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),再执行子类构造方法体,形成构造链。
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 复用父类逻辑,再扩展自己的逻辑。
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,子类只写各自特有行为。
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,因此所有对象都有equals、hashCode、toString等方法,可重写以符合业务语义。
注意
重写 equals 时务必同时重写 hashCode,否则在基于哈希的集合(如 HashMap、HashSet)中行为异常。详见 常用类。