1. Java重写机制概述
在面向对象编程中,重写(Override)是Java语言实现多态性的核心机制之一。当子类需要改变或扩展父类方法的行为时,可以通过方法重写来实现这一需求。与重载(Overload)不同,重写要求方法签名完全相同,包括方法名、参数列表和返回类型(协变返回类型除外)。
关键区别:重写发生在继承关系的父子类之间,而重载发生在同一个类中,只需方法名相同但参数列表不同。
2. 方法重写的核心规则
2.1 基本语法要求
要正确重写父类方法,必须遵循以下语法规则:
- 方法名称必须完全相同
- 参数列表必须完全一致
- 返回类型应当相同或是父类方法返回类型的子类(协变返回)
- 访问修饰符不能比父类方法更严格(public > protected > default > private)
class Animal { public void makeSound() { System.out.println("Animal makes sound"); } } class Dog extends Animal { @Override // 推荐使用注解 public void makeSound() { System.out.println("Dog barks"); } }2.2 访问权限控制
子类重写方法时,访问权限可以放宽但不能收紧:
- 父类protected方法可以重写为public
- 父类public方法不能重写为protected或private
- 父类private方法实际上不能被重写(因为不可见)
2.3 异常处理规则
- 重写方法不能抛出比父类方法更宽泛的检查型异常
- 可以抛出更具体的异常或不抛出异常
- 非检查型异常(RuntimeException)不受限制
3. 重写的实现细节
3.1 @Override注解的作用
虽然语法上不是必须的,但强烈建议使用@Override注解:
- 编译器会检查是否真的重写了父类方法
- 提高代码可读性,明确表明这是重写方法
- 防止因拼写错误导致意外创建新方法
3.2 协变返回类型
Java 5+支持协变返回类型,允许子类重写方法返回父类方法返回类型的子类:
class ShapeFactory { public Shape create() { return new Shape(); } } class CircleFactory extends ShapeFactory { @Override public Circle create() { // Circle是Shape的子类 return new Circle(); } }3.3 final方法的重写限制
被final修饰的方法不能被重写:
- 用于防止关键方法被修改
- 常见于工具类方法或需要保持行为一致的方法
4. 重写与动态绑定
4.1 运行时多态实现
Java通过动态方法调度(Dynamic Method Dispatch)实现运行时多态:
- 编译时检查方法签名
- 运行时根据实际对象类型调用对应方法
Animal myDog = new Dog(); myDog.makeSound(); // 输出"Dog barks"而非"Animal makes sound"4.2 方法表机制
JVM使用虚方法表(vtable)实现动态绑定:
- 每个类维护一个方法表
- 包含该类所有可被重写的方法指针
- 子类方法表会复制父类方法表并覆盖重写的方法
5. 常见问题与最佳实践
5.1 常见错误场景
- 意外重载而非重写(参数列表不一致)
- 试图重写static/final/private方法
- 访问权限收紧导致编译错误
- 异常声明不兼容
5.2 设计建议
- 尽量使用@Override注解
- 保持重写方法行为与父类约定一致(Liskov替换原则)
- 避免在构造方法中调用可重写方法
- 考虑使用模板方法模式规范重写行为
5.3 性能考量
- 动态绑定比静态绑定有轻微性能开销
- 热点代码可考虑使用final修饰防止重写
- JIT优化会针对单态调用做去虚化处理
6. 高级应用场景
6.1 抽象方法重写
抽象方法必须被具体子类重写实现:
abstract class Vehicle { public abstract void start(); } class Car extends Vehicle { @Override public void start() { System.out.println("Car starts with key"); } }6.2 接口默认方法重写
Java 8+允许接口定义默认方法,实现类可以选择重写:
interface Logger { default void log(String message) { System.out.println("LOG: " + message); } } class FileLogger implements Logger { @Override public void log(String message) { // 写入文件而非控制台 } }6.3 桥接方法处理
编译器可能生成桥接方法(bridge method)处理泛型类型擦除带来的重写问题,这些方法通常带有synthetic标记。
7. 重写与其他特性的交互
7.1 与重载的对比
| 特性 | 重写(Override) | 重载(Overload) |
|---|---|---|
| 作用范围 | 父子类之间 | 同一个类中 |
| 方法签名 | 必须完全相同 | 必须不同(参数列表) |
| 返回类型 | 必须兼容 | 可以不同 |
| 异常 | 有限制 | 无限制 |
| 访问权限 | 不能更严格 | 可以不同 |
7.2 与多线程的交互
- 重写方法需要考虑线程安全性
- synchronized不是方法签名的一部分,可以自由添加/移除
- 但改变同步策略可能破坏线程安全保证
7.3 与序列化的关系
- readObject/writeObject方法可以被重写以定制序列化行为
- 重写equals必须同时重写hashCode
- 克隆时需要谨慎处理重写的clone方法
8. 实际开发中的经验
框架中的重写:Spring等框架大量使用重写机制,如:
- @RequestMapping注解方法
- 模板方法回调
- 事件处理器实现
调试技巧:
- 使用IDE的"Go to Implementation"跳转到重写方法
- 断点调试时注意动态绑定带来的方法调用
- 使用getClass()确认运行时类型
代码审查要点:
- 检查重写方法是否保持了行为约定
- 验证异常声明是否兼容
- 确认访问权限没有不当收紧
性能优化:
- 对性能关键方法考虑使用final
- 避免深层次继承带来的方法查找开销
- 使用JProfiler分析虚方法调用热点