java重载与重写
Java 中的重载(Overload)与重写(Override)详解
在 Java 中,重载(Overload)和重写(Override)是两个容易混淆但意义完全不同的概念。它们都是实现代码复用和多态性的重要手段,但应用场景和规则截然不同。本文将从定义、规则、示例及区别等方面,全面解析两者的核心差异。
重载(Overload):同一类中的同名方法
重载指在同一个类中,存在多个方法名称相同但参数列表不同的方法。编译器通过参数列表的差异来区分不同的方法,与返回值类型、异常类型无关。
重载的核心规则
- 方法名称必须相同:如
add(int a)和add(int a, int b)是重载。 - 参数列表必须不同:
- 参数个数不同(如
add(int a)vsadd(int a, int b)); - 参数类型不同(如
add(int a)vsadd(double a)); - 参数顺序不同(如
add(int a, String b)vsadd(String a, int b))。
- 参数个数不同(如
- 与返回值类型无关:仅返回值不同不能构成重载(如
int add(int a)vsdouble add(int a)会编译报错)。 - 与访问修饰符、异常类型无关:修饰符或异常不同不影响重载判断,只要参数列表不同即可。
示例:方法重载
public class Calculator {
// 1. 一个参数的加法
public int add(int a) {
return a;
}
// 2. 两个int参数的加法(参数个数不同)
public int add(int a, int b) {
return a + b;
}
// 3. 两个double参数的加法(参数类型不同)
public double add(double a, double b) {
return a + b;
}
// 4. 参数顺序不同(int在前 vs String在前)
public String add(int a, String b) {
return b + a;
}
public String add(String a, int b) {
return a + b;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(1)); // 调用1
System.out.println(calc.add(1, 2)); // 调用2
System.out.println(calc.add(1.5, 2.5)); // 调用3
System.out.println(calc.add(3, "个")); // 调用4
System.out.println(calc.add("共", 5)); // 调用5
}
}
- 执行结果:编译器根据传入的参数类型和个数,自动匹配对应的重载方法,输出正确结果。
重载的应用场景
- 简化方法命名:用相同名称表示语义相似的操作(如
add可表示不同参数的加法)。 - 提高代码可读性:避免为相似功能起不同名称(如
addInt、addDouble),减少记忆负担。
重写(Override):子类覆盖父类方法
重写指子类中定义的方法与父类中的某个方法名称、参数列表完全相同,从而覆盖父类方法的实现。重写是面向对象多态性的核心体现,允许子类根据自身需求修改父类的行为。
重写的核心规则(“两同两小一大”)
- 方法名称和参数列表必须完全相同:与父类方法的名称、参数类型、顺序、个数完全一致(否则为重载)。
- 返回值类型:
- 基本类型:必须与父类完全相同;
- 引用类型:子类返回值可以是父类返回值的子类(协变返回类型,JDK 5+ 支持)。
- 访问修饰符:子类方法的访问权限不能小于父类方法(如父类为
protected,子类可改为public,但不能改为private)。 - 异常类型:子类方法抛出的异常不能多于或宽于父类方法(可抛出更少或更具体的异常)。
- 静态方法不能重写:子类若定义与父类同名的静态方法,属于 “隐藏” 而非重写(编译不会报错,但不具备多态性)。
- 私有方法不能重写:父类的
private方法对子类不可见,子类定义同名方法视为全新方法。
示例:方法重写
// 父类
class Animal {
protected void eat() {
System.out.println("动物吃东西");
}
public void move() {
System.out.println("动物移动");
}
public static void sleep() { // 静态方法
System.out.println("动物睡觉");
}
}
// 子类
class Dog extends Animal {
// 重写父类的eat方法(访问权限扩大:protected → public)
public void eat() {
System.out.println("狗吃骨头");
}
// 重写父类的move方法(返回值相同,参数相同)
public void move() {
System.out.println("狗跑");
}
// 静态方法:隐藏父类方法(非重写)
public static void sleep() {
System.out.println("狗睡觉");
}
}
// 测试多态
public class TestOverride {
public static void main(String[] args) {
Animal animal = new Dog(); // 父类引用指向子类对象
animal.eat(); // 调用子类重写的eat(多态)→ 输出"狗吃骨头"
animal.move(); // 调用子类重写的move → 输出"狗跑"
animal.sleep(); // 调用父类静态方法(无多态)→ 输出"动物睡觉"
}
}
@Override注解:显式标记方法为重写,编译器会检查是否符合重写规则,避免误写(如参数错误)。
重写的应用场景
- 多态实现:通过父类引用调用子类重写的方法,实现 “同一接口,不同实现”。
- 功能扩展:在保留父类核心逻辑的基础上,子类可通过
super调用父类方法并添加新逻辑。
public void eat() {
super.eat(); // 调用父类的eat方法
System.out.println("狗吃完骨头舔爪子"); // 子类扩展逻辑
}
隐藏字段(Field Hiding)
与方法不同,父类和子类的同名实例变量不会发生重写,而是 “隐藏”—— 子类的字段会覆盖父类的字段,但不具备多态性,访问时由声明类型决定。
class Parent {
public String name = "父类字段";
}
class Child extends Parent {
public String name = "子类字段"; // 隐藏父类字段
}
public class TestField {
public static void main(String[] args) {
Parent p = new Child();
System.out.println(p.name); // 声明类型为Parent → 输出"父类字段"
Child c = new Child();
System.out.println(c.name); // 声明类型为Child → 输出"子类字段"
}
}
- 注意:字段隐藏是不良实践,通常应将字段设为
private并用getter方法访问,避免歧义。
重载与重写的核心区别
| 维度 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 定义 | 同一类中,方法名相同,参数列表不同 | 子类中,方法名和参数列表与父类完全相同 |
| 绑定时机 | 编译期绑定(静态绑定) | 运行期绑定(动态绑定) |
| 多态性 | 不体现多态(仅编译期区分) | 体现多态(父类引用调用子类实现) |
| 权限修饰符 | 无限制 | 子类权限 ≥ 父类权限 |
| 返回值类型 | 无限制(与重载无关) | 基本类型必须相同;引用类型可协变 |
| 异常 | 无限制 | 子类异常 ≤ 父类异常(范围和数量) |
| 适用范围 | 同一类或父子类(但通常在同一类) | 仅父子类 |
| 静态方法 | 可重载 | 不可重写(仅能隐藏) |