"重载是静态绑定,重写是动态绑定"是背得很熟的一句话。真实构造一个 Animal/Dog 场景,让重载和重写在同一段代码里正面对比,一次性看清两者真实依据的是不同的类型信息。
上一篇:类到底什么时候被初始化:真实验证编译期常量根本不会触发它
用同一个变量 a(声明类型 Animal,运行时真实类型 Dog),分别调用一个被重写的方法和一组重载的方法,对比两者的真实分派结果。
OverloadOverrideDemo.java static class Animal { String sound() { return "某种动物的叫声"; } } static class Dog extends Animal { @Override String sound() { return "汪汪"; } } static String describe(Animal a) { return "重载版本(Animal) 处理: " + a.sound(); } static String describe(Dog d) { return "重载版本(Dog) 处理: " + d.sound(); } Animal a = new Dog(); // 声明类型 Animal,运行时真实类型 Dog
这部分符合大多数人的直觉预期。
a 的编译期声明类型 = Animal, 运行时真实类型 = Dog a.sound() 真实调用结果 = 汪汪(重写方法真实按运行时类型 Dog 分派)
这部分是最容易和"重写"混淆、真实结果和直觉不一致的地方。
describe(a) 真实选中的重载版本 = 重载版本(Animal) 处理: 汪汪 (变量 a 运行时真实是 Dog,但因为声明类型是 Animal,重载解析在编译期就真实选定了 describe(Animal) 版本) describe(realDog) 真实选中的重载版本 = 重载版本(Dog) 处理: 汪汪 (这次声明类型就是 Dog,重载解析真实选中了 describe(Dog) 版本) (Animal) realDog 强转后再调用 describe: 重载版本(Animal) 处理: 汪汪
三行真实结果拼出完整的规律:调用 describe(a) 时,a 运行时的真实对象和调用 describe(realDog) 时完全一样(都是同一个 Dog 实例的行为),但因为变量 a 的声明类型是 Animal,编译器在编译这行代码的那一刻,就已经真实、永久地决定了要调用哪个重载版本——这个决定写进了字节码里(生成的是对 describe(Animal) 这个具体方法签名的 invokestatic 调用),运行时不会再重新判断。第三行更直接:把同一个 Dog 实例强转成 Animal 类型再传进去,重载解析结果立刻真实跟着声明类型变了。
每一行都是真实运行这段代码得到的结果。