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);
}
类型提升规则总结:
byte
→short
,int
,long
,float
,double
short
→int
,long
,float
,double
char
→int
,long
,float
,double
int
→long
,float
,double
long
→float
,double
float
→double
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 获取。