为什么要引入子类,而不是直接讨论父类的虚方法表?
关键点:
- 如果你创建的是
Animal animal = new Animal();
,那么animal.makeSound()
只会调用Animal
自己的方法,不涉及多态,不涉及方法重写,自然也不会动态查找子类的方法。 - 虚方法表的核心作用是支持多态,即 父类引用指向子类对象,并调用子类的方法。因此,只有当
Animal
变量指向Dog
实例时,虚方法表才会发生动态查找和方法覆盖的行为。
1. 仅使用父类对象,无法体现虚方法表的动态绑定
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound(); // 输出:Animal sound
}
}
这里的过程:
animal.makeSound();
直接调用Animal
的makeSound()
方法。- 由于
Animal
只存在自己的makeSound()
方法,JVM 直接在Animal
的vtable
里找到makeSound()
并调用。 - 这里没有多态行为,也不涉及动态绑定。
2. 只有父类引用指向子类对象时,才会触发虚方法表查找
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
animal.makeSound(); // 输出:Dog barks
}
}
这里的过程:
- 变量
animal
的编译时类型是Animal
,但实际运行时类型是Dog
。 animal.makeSound();
方法被调用时:- 编译时
animal
看起来是Animal
,所以它合法调用makeSound()
。 - 运行时
animal
其实指向Dog
,JVM 先找到Animal
类的vtable
,但由于Dog
重写了makeSound()
,虚方法表指针被替换为Dog::makeSound()
。 - 最终 JVM 在
Dog
的vtable
里找到makeSound()
方法,并执行Dog
的实现。
- 编译时
- 这里体现了动态绑定,JVM 通过虚方法表找到实际的
Dog::makeSound()
方法,而不是Animal::makeSound()
。
3. 虚方法表的意义在于支持多态,而非单独存在
如果没有子类,虚方法表几乎没有作用,因为它的主要目标是支持方法重写和动态绑定。
- 只有 方法被重写(override)时,虚方法表才会动态替换指向的具体方法。
- 如果只是
Animal animal = new Animal();
,那么方法查找就是静态的,不涉及方法重写、虚方法表跳转。
举个比喻:
- 单独的
Animal
类就像一个人自己保存的电话号码,他只会打自己的电话,不需要查找别人的号码。 Animal animal = new Dog();
就像是一个助理(父类引用)帮你拨号,但具体打给谁(子类方法)取决于助理手中的通讯录(虚方法表)。
4. 总结
- 如果只使用
Animal animal = new Animal();
,则调用的是Animal
自己的方法,不涉及多态,不使用虚方法表的动态查找。 - 只有当
Animal animal = new Dog();
(向上转型)时,Java 才会利用虚方法表查找Dog::makeSound()
,从而实现多态。 - 虚方法表的本质是支持方法重写和动态绑定,而方法重写只有在父类-子类的继承关系中才会发生。
因此,虚方法表的讨论必然涉及子类和父类的关系,否则它的意义就不存在了。