Java 继承

更新于 2025-12-25

Jakob Jenkov 2015-09-04

Java 中的继承(Inheritance)是指一个类可以从另一个类继承属性和行为。在 Java 中,这也被称为“扩展”(extending)一个类。当一个类继承另一个类时,这两个类就分别扮演了特定的角色:

  • 子类(subclass):执行继承操作的类,也称为派生类。
  • 父类(superclass):被继承的类,也称为基类。

换句话说,子类扩展了父类,或者说子类从父类继承而来。

另一个常用于描述继承关系的术语是特化(specialization)泛化(generalization)

  • 子类是对父类的一种特化;
  • 父类是对一个或多个子类的泛化。

继承是一种代码复用的方法

继承是一种有效的方式,用于在具有某些共同特征但又存在差异的类之间共享代码。

下图展示了一个名为 Vehicle(车辆)的类,它有两个子类:Car(小汽车)和 Truck(卡车)。

classDiagram Vehicle <|-- Car Vehicle <|-- Truck class Car {} class Truck {}
  • VehicleCarTruck 的父类;
  • CarTruckVehicle 的子类。

Vehicle 类可以包含所有车辆共有的字段和方法(例如车牌号、车主等),而 CarTruck 则可以各自包含其特有的字段和方法。

注意:有些人认为继承是根据“是什么”来对类进行分类的。例如,“小汽车是一种车辆”,“卡车也是一种车辆”。但在实际开发中,是否使用继承通常不是由这种语义决定的,而是取决于你在应用程序中如何处理这些对象。

例如,你是否需要将 CarTruck 对象统一视为 Vehicle 对象?是否需要以相同方式处理它们?如果是,那么为它们定义一个公共的 Vehicle 父类是有意义的。如果从未以相同方式处理它们,那么除了避免重复代码外,就没有必要创建公共父类。


类层次结构(Class Hierarchies)

父类和子类构成了一个继承结构,也称为类层次结构(class hierarchy)。在该结构的顶部是父类,底部是子类。

类层次结构可以有多层:一个子类本身也可以作为其他类的父类,从而形成多级继承。


Java 继承基础

当一个类从父类继承时,它会继承父类的部分方法和字段。子类还可以重写(override)继承来的方法。字段不能被重写,但可以在子类中被“隐藏”(shadowing)。具体机制将在后文详细说明。


哪些内容会被继承?

在 Java 中,当子类继承父类时:

  • 所有 protectedpublic 的字段和方法都会被继承;
  • 被继承的成员在子类中就像子类自己声明的一样,可以直接访问;
  • 具有默认(包级)访问权限的成员,只有在子类与父类位于同一包中时才能被访问;
  • private 字段和方法永远不会被子类直接访问,但可以通过父类中可访问的方法(如 protectedpublic 方法)间接访问。

构造函数不会被继承。但是,子类的构造函数必须调用父类的某个构造函数(这将在后文详述)。


Java 仅支持单继承

Java 的继承机制只允许一个类继承自一个父类(即单继承)。某些语言(如 C++)支持多继承(一个类可继承多个父类),但由于多继承可能引发命名冲突等问题(例如多个父类中有同名同参的方法),Java 有意不支持多继承。


在 Java 中声明继承

在 Java 中,使用 extends 关键字声明继承关系。示例如下:

public class Vehicle {
    protected String licensePlate = null;

    public void setLicensePlate(String license) {
        this.licensePlate = license;
    }
}

public class Car extends Vehicle {
    int numberOfSeats = 0;

    public String getNumberOfSeats() {
        return this.numberOfSeats;
    }
}

在这个例子中,Car 类继承自 Vehicle 类,因此它自动拥有 licensePlate 字段(因为它是 protected 的)。

虽然上面的代码没有在 Car 中直接使用 licensePlate,但我们完全可以这样做:

public class Car extends Vehicle {
    int numberOfSeats = 0;

    public String getNumberOfSeats() {
        return this.numberOfSeats;
    }

    public String getLicensePlate() {
        return this.licensePlate; // 访问继承自父类的字段
    }
}

注意:通常更合理的做法是将 getLicensePlate() 方法放在 Vehicle 类中,这里只是为了演示子类可以访问继承的字段。


继承与类型转换(Type Casting)

你可以将子类的实例当作其父类的实例来使用。例如:

Car car = new Car();
Vehicle vehicle = car; // 合法:Car 是 Vehicle 的子类

这个过程称为类型转换(type casting)。

向上转型(Upcasting)

将子类对象赋值给父类引用,称为向上转型。这是总是安全且自动的:

Car car = new Car();
Vehicle vehicle = car; // 向上转型

向下转型(Downcasting)

将父类引用转回子类类型,称为向下转型。但前提是该对象实际是该子类的实例,否则会在运行时抛出 ClassCastException

✅ 正确示例:

Car car = new Car();
Vehicle vehicle = car;          // 向上转型
Car car2 = (Car) vehicle;       // 向下转型,成功

❌ 错误示例:

Truck truck = new Truck();
Vehicle vehicle = truck;        // 向上转型
Car car = (Car) vehicle;        // 运行时抛出 ClassCastException!

重写方法(Overriding Methods)

子类可以重写(override)父类中定义的方法。例如:

public class Vehicle {
    String licensePlate = null;
    public void setLicensePlate(String licensePlate) {
        this.licensePlate = licensePlate;
    }
}

public class Car extends Vehicle {
    @Override
    public void setLicensePlate(String license) {
        this.licensePlate = license.toLowerCase(); // 转为小写
    }
}

要成功重写,子类方法的签名(方法名、参数类型和顺序)必须与父类完全一致。否则会被视为一个新方法,而非重写。

注意private 方法无法被重写。即使子类定义了同名同参的 private 方法,父类内部调用仍会使用自己的 private 方法。


@Override 注解

使用 @Override 注解可以明确表示该方法意在重写父类方法。如果父类中该方法被删除或签名变更,编译器会报错,帮助你及时发现问题:

public class Car extends Vehicle {
    @Override
    public void setLicensePlate(String license) {
        this.licensePlate = license.toLowerCase();
    }
}

这是一个良好的编程实践。


调用父类方法

即使重写了父类方法,你仍然可以通过 super 关键字调用父类的实现:

public class Car extends Vehicle {
    @Override
    public void setLicensePlate(String license) {
        super.setLicensePlate(license); // 调用父类方法
        // 可在此添加额外逻辑
    }
}

你可以在子类的任何方法中使用 super,不一定非要在重写的方法中。


instanceof 操作符

Java 提供 instanceof 操作符,用于判断一个对象是否是某个类(或其子类)的实例:

Car car = new Car();
boolean isCar = car instanceof Car;        // true
boolean isVehicle = car instanceof Vehicle; // true(因为 Car 继承自 Vehicle)

即使变量声明为父类类型,instanceof 仍会检查实际对象的类型

Vehicle vehicle = new Car();
boolean isCar = vehicle instanceof Car;    // true

但如果实际对象不是目标类型,则返回 false

Vehicle vehicle = new Truck();
boolean isCar = vehicle instanceof Car;    // false

字段与继承

字段不能被重写。如果子类定义了与父类同名的字段,该字段会隐藏(shadow)父类字段。

示例:

public class Vehicle {
    String licensePlate = null;
    public String getLicensePlate() { return licensePlate; }
    public void setLicensePlate(String s) { licensePlate = s; }
}

public class Car extends Vehicle {
    protected String licensePlate = null; // 隐藏父类字段

    @Override
    public void setLicensePlate(String license) {
        super.setLicensePlate(license); // 设置父类字段
    }

    @Override
    public String getLicensePlate() {
        return super.getLicensePlate(); // 读取父类字段
    }

    public void updateLicensePlate(String license) {
        this.licensePlate = license; // 设置子类字段!
    }
}

测试代码:

Car car = new Car();
car.setLicensePlate("123");
car.updateLicensePlate("abc");
System.out.println(car.getLicensePlate()); // 输出 "123"

原因:getLicensePlate() 返回的是父类的 licensePlate(值为 "123"),而 updateLicensePlate() 修改的是子类的 licensePlate(值为 "abc"),两者是不同的字段

建议:尽量避免在子类中定义与父类同名的字段,以免造成混淆。


构造函数与继承

构造函数不会被继承。但子类的构造函数必须在第一行调用父类的某个构造函数(显式或隐式)。

显式调用:

public class Vehicle {
    public Vehicle() { }
}

public class Car extends Vehicle {
    public Car() {
        super(); // 调用父类构造函数
    }
}

隐式调用:

如果子类构造函数未显式调用 super(),Java 编译器会自动插入对父类无参构造函数的调用。

如果父类没有无参构造函数,则子类必须显式调用父类的某个带参构造函数,否则编译失败。


嵌套类与继承

嵌套类(Nested Classes)同样遵循继承规则:

  • private 嵌套类不会被继承;
  • 默认(包级)访问权限的嵌套类,仅当子类与父类在同一包中时才可访问;
  • protectedpublic 嵌套类总是可被继承。

示例:

class MyClass {
    class MyNestedClass { }
}

public class MySubclass extends MyClass {
    public static void main(String[] args) {
        MySubclass subclass = new MySubclass();
        MyNestedClass nested = subclass.new MyNestedClass(); // 合法
    }
}

final 类与继承

使用 final 修饰的类不能被继承

public final class MyClass { }
// 以下代码非法:
// public class SubClass extends MyClass { } // 编译错误

抽象类与继承

抽象类(abstract class)是专门设计用来被继承的类。它不能被实例化,但可以包含抽象方法(无实现)和具体方法。

子类继承抽象类时,必须实现所有抽象方法(除非子类也是抽象类)。

抽象类的继承规则与其他类相同。

更多关于抽象类的内容,请参阅 Java 抽象类教程