1. 概述

方法重载(Method Overloading)和方法重写(Method Overriding)是 Java 编程语言的核心概念,值得深入探讨。本文将剖析这些基础概念及其适用场景,帮助开发者写出更优雅的代码。

2. 方法重载

方法重载是构建统一类 API 的强大机制。通过一个简单示例理解其价值:假设我们编写了一个工具类,分别实现两数相乘、三数相乘等方法。如果方法名设计为 multiply2()multiply3() 等,这显然是糟糕的 API 设计。此时方法重载就派上用场了。

实现方法重载有两种方式:

  • 参数数量不同:同名方法但参数个数不同
  • 参数类型不同:同名方法但参数类型不同

2.1 参数数量不同

Multiplier 类展示了通过参数数量重载 multiply() 方法的实现:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
}

2.2 参数类型不同

同样可通过参数类型重载方法:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

两种重载方式可同时使用:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

⚠️ 注意:不能仅通过返回类型区分重载方法。以下代码会导致编译错误:

public int multiply(int a, int b) { 
    return a * b; 
}
 
public double multiply(int a, int b) { 
    return a * b; 
}

编译器无法确定调用哪个实现,直接报错。

2.3 类型提升

方法重载支持类型提升(Type Promotion),即当参数类型不匹配时,自动将参数提升为更宽的类型。观察以下实现:

public double multiply(int a, long b) {
    return a * b;
}

public int multiply(int a, int b, int c) {
    return a * b * c;
}

调用 multiply(10, 10) 时,第二个 int 参数会被提升为 long

@Test
public void whenCalledMultiplyAndNoMatching_thenTypePromotion() {
    assertThat(multiplier.multiply(10, 10)).isEqualTo(100.0);
}

但当参数完全匹配时,类型提升不会发生:

@Test
public void whenCalledMultiplyAndMatching_thenNoTypePromotion() {
    assertThat(multiplier.multiply(10, 10, 10)).isEqualTo(1000);
}

类型提升规则总结:

  • byteshort, int, long, float, double
  • shortint, long, float, double
  • charint, long, float, double
  • intlong, float, double
  • longfloat, double
  • floatdouble

2.4 静态绑定

方法调用与方法体的关联称为绑定(Binding)。重载方法的绑定在编译时完成,称为静态绑定。编译器通过检查方法签名即可确定调用目标。

3. 方法重写

方法重写允许子类为父类方法提供更精细的实现。作为继承的产物,方法重写是 OOP 的核心特性,但需谨慎使用——滥用可能导致违反里氏替换原则(LSP)

通过继承关系演示重写机制。父类 Vehicle

public class Vehicle {
    
    public String accelerate(long mph) {
        return "The vehicle accelerates at : " + mph + " MPH.";
    }
    
    public String stop() {
        return "The vehicle has stopped.";
    }
    
    public String run() {
        return "The vehicle is running.";
    }
}

子类 Car 重写 accelerate() 方法:

public class Car extends Vehicle {

    @Override
    public String accelerate(long mph) {
        return "The car accelerates at : " + mph + " MPH.";
    }
}

重写后的方法签名与返回类型必须一致。验证父子类行为:

@Test
public void whenCalledAccelerate_thenOneAssertion() {
    assertThat(vehicle.accelerate(100))
      .isEqualTo("The vehicle accelerates at : 100 MPH.");
}
    
@Test
public void whenCalledRun_thenOneAssertion() {
    assertThat(vehicle.run())
      .isEqualTo("The vehicle is running.");
}
    
@Test
public void whenCalledStop_thenOneAssertion() {
    assertThat(vehicle.stop())
      .isEqualTo("The vehicle has stopped.");
}

@Test
public void whenCalledAccelerate_thenOneAssertion() {
    assertThat(car.accelerate(80))
      .isEqualTo("The car accelerates at : 80 MPH.");
}
    
@Test
public void whenCalledRun_thenOneAssertion() {
    assertThat(car.run())
      .isEqualTo("The vehicle is running.");
}
    
@Test
public void whenCalledStop_thenOneAssertion() {
    assertThat(car.stop())
      .isEqualTo("The vehicle has stopped.");
}

未重写的方法(run()/stop())在父子类中行为一致:

@Test
public void givenVehicleCarInstances_whenCalledRun_thenEqual() {
    assertThat(vehicle.run()).isEqualTo(car.run());
}
 
@Test
public void givenVehicleCarInstances_whenCalledStop_thenEqual() {
   assertThat(vehicle.stop()).isEqualTo(car.stop());
}

重写方法在子类中返回不同值:

@Test
public void whenCalledAccelerateWithSameArgument_thenNotEqual() {
    assertThat(vehicle.accelerate(100))
      .isNotEqualTo(car.accelerate(100));
}

3.1 类型可替换性

OOP 的核心原则是类型可替换性,与里氏替换原则(LSP)紧密关联。LSP 要求:如果程序适用于基类,则必须适用于其所有子类

方法重写最大的坑在于:子类实现可能违反 LSP,破坏类型可替换性。重写时需遵守规则:

  • ✅ 参数类型:必须与父类相同或是其父类型(逆变)
  • ✅ 返回类型:必须与父类相同或是其子类型(协变)
  • ✅ 异常类型:必须与父类相同或是其子类型
  • ✅ 访问修饰符:不能比父类更严格

3.2 动态绑定

方法重写依赖继承关系。编译时无法确定调用哪个方法(父类或子类),需在运行时检查对象类型。这种机制称为动态绑定

4. 总结

本文深入探讨了 Java 方法重载与重写的实现机制及适用场景。重载通过静态绑定提供编译时多态,重写通过动态绑定实现运行时多态。合理使用这些特性能显著提升代码的可读性和扩展性。

本文所有代码示例可在 GitHub 获取。


原始标题:Method Overloading and Overriding in Java | Baeldung