Skip to content

Java 方法重写

方法重写是面向对象编程 (OOP) 中的一个基本概念,也是 Java 中多态性的一个关键特性。它允许子类(派生类)为已在其超类(父类)中定义的方法提供一个特定的实现。

当子类重写一个方法时,它实际上是用自己的版本替换了继承的实现。这使得子类对象能够展示其类型的特定行为,即使是通过超类类型的变量引用的也是如此。

  • 专用行为 (Specialized Behavior): 允许子类定义针对其特定需求的行为,同时遵循超类方法定义的契约。
  • 运行时多态性 (Runtime Polymorphism): 使得 Java 虚拟机 (JVM) 能够在运行时根据实际对象类型(而非引用变量类型)确定执行哪个方法版本(超类的或子类的)。

考虑一个 Vehicle 超类和一个 Car 子类。

// 超类
class Vehicle {
String brand;
public Vehicle(String brand) {
this.brand = brand;
}
public void displayInfo() {
System.out.println("Vehicle Brand: " + brand);
}
public void startEngine() {
System.out.println("Vehicle engine starting...");
}
}
// 子类
class Car extends Vehicle {
int numberOfDoors;
public Car(String brand, int doors) {
super(brand); // 调用超类构造器
this.numberOfDoors = doors;
}
// --- 重写 displayInfo 方法 ---
@Override // 注解确保正确重写(推荐!)
public void displayInfo() {
System.out.println("Car Brand: " + brand + ", Doors: " + numberOfDoors);
}
// Car 特有方法
public void honk() {
System.out.println("Car honking: Beep beep!");
}
}
public class TestVehicle {
public static void main(String[] args) {
Vehicle genericVehicle = new Vehicle("Generic");
Vehicle myCar = new Car("Toyota", 4); // Vehicle 引用,Car 对象
Car anotherCar = new Car("Honda", 2);
System.out.println("--- Calling displayInfo() ---");
genericVehicle.displayInfo(); // 调用 Vehicle 的版本
myCar.displayInfo(); // 调用 Car 的重写版本(运行时多态性)
anotherCar.displayInfo(); // 调用 Car 的重写版本
System.out.println("\n--- Calling startEngine() --- (继承的)");
genericVehicle.startEngine();
myCar.startEngine(); // 从 Vehicle 继承
anotherCar.startEngine();
System.out.println("\n--- Calling car-specific method --- ");
// myCar.honk(); // 编译错误!'myCar' 是 Vehicle 类型,不知道 'honk()' 方法
anotherCar.honk(); // 正常工作,'anotherCar' 是 Car 类型
// 要在 myCar 上调用 honk(),需要进行类型转换:
if (myCar instanceof Car) { // 在类型转换前检查类型
((Car) myCar).honk();
}
}
}

输出:

--- Calling displayInfo() ---
Vehicle Brand: Generic
Car Brand: Toyota, Doors: 4
Car Brand: Honda, Doors: 2
--- Calling startEngine() --- (Inherited)
Vehicle engine starting...
Vehicle engine starting...
Vehicle engine starting...
--- Calling car-specific method ---
Car honking: Beep beep!
Car honking: Beep beep!
  • Car 类 extends Vehicle,继承其字段和方法。
  • Car 提供了自己的 displayInfo 方法,并用 @Override 标记。此版本打印 Car 特有细节。
  • 当调用 myCar.displayInfo() 时,即使 myCar 被声明为 Vehicle 类型,JVM 在运行时确定实际对象是一个 Car。因此,它执行 Car 类中重写的 displayInfo 方法。(运行时多态性)
  • startEngine() 没有被重写,所以 genericVehicle 和 myCar 都使用从 Vehicle 继承的实现。
  • honk() 方法仅存在于 Car 中。如果不用类型转换,你不能直接在 Vehicle 引用(myCar)上调用它,因为编译器只知道 Vehicle 类(或其超类)中定义的方法。

强烈建议在打算重写超类方法时使用 @Override 注解。

  • 编译时检查: 编译器验证带注解的方法是否确实重写了超类中的方法或实现了接口中的方法。如果出现错误(例如,拼错方法名,参数不正确),你会在编译时获得错误,而不是遇到微妙的运行时 bug。
  • 可读性: 清楚地表明程序员的重写意图,使代码更容易理解。

要正确重写方法,必须遵循以下规则:

  • 方法签名 (Method Signature): 子类中的重写方法必须具有与超类中方法完全相同的名称和参数列表(参数的数量、类型和顺序)。
  • 返回类型 (Return Type): 返回类型必须与超类方法中声明的返回类型相同,或者是其子类型(协变返回类型)。(例如,如果超类返回 Vehicle,子类可以返回 Car)。
  • 访问修饰符 (Access Modifier): 重写方法的访问级别不能比被重写方法的访问级别更严格。(例如,如果超类方法是 public,子类方法不能是 protected 或 private。如果超类是 protected,子类可以是 protected 或 public)。
  • final 方法: 在超类中声明为 final 的方法不能被重写。
  • static 方法: 声明为 static 的方法不能被重写(它们属于类,而非实例)。如果子类声明了一个具有相同签名的静态方法,这被称为方法隐藏,而不是重写。
  • private 方法: 声明为 private 的方法不被继承,因此不能被重写。
  • 构造器 (Constructors): 构造器不被继承,也不能被重写。
  • 异常 (Exceptions): 重写方法可以抛出:
    • 任何未受检异常(RuntimeException 或 Error 的子类),无论被重写方法抛出什么。
    • 与被重写方法抛出的相同的受检异常(Exception 的子类但不是 RuntimeException)。
    • 被重写方法抛出的受检异常的子类。
    • 比被重写方法抛出的受检异常少,或者完全不抛出受检异常。
    • 它不能抛出比被重写方法声明的异常更新或范围更广的受检异常。

有时,在重写方法内部,您可能希望在添加子类特有行为之前执行超类的原始实现。您可以使用 super 关键字来实现这一点。

super.methodName() 调用超类的方法版本。

class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
super.makeSound(); // 首先调用 Animal 的 makeSound() 方法
System.out.println("Dog barks: Woof woof!"); // 添加 Dog 特有行为
}
}
public class TestSuper {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.makeSound();
}
}

输出:

Animal makes a sound
Dog barks: Woof woof!

重写 (Overriding) vs. 重载 (Overloading)

Section titled “重写 (Overriding) vs. 重载 (Overloading)”

不要混淆重写 (overriding) 和重载 (overloading):

特性方法重写 (Method Overriding)方法重载 (Method Overloading)
目的在子类中为继承的方法提供特定实现。在同一类内提供多个同名但参数不同的方法。
方法签名相同名称,相同参数列表。相同名称,不同参数列表(数量、类型或顺序)。
关系发生在超类和子类之间(继承)。发生在同一类内。
返回类型可以相同或协变。可以不同(仅凭返回类型不能区分)。
多态性运行时多态性(动态绑定)。编译时多态性(静态绑定)。
示例Car.displayInfo() 重写 Vehicle.displayInfo()。Calculator.add(int, int) 和 Calculator.add(double, double)。

方法重写是多态性的基石。下一章“Java 多态性”将更详细地探讨这一概念,展示重写如何实现灵活且可扩展的面向对象设计。