报错代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.torch;

public class PolymorphismDemo {

static class Animal {
public void eat(){
System.out.println("动物吃饭!");
}
public void work(){
System.out.println("动物可以帮助人类干活!");
}
}

static class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void sleep() {
System.out.println("猫会睡懒觉");
}
}

static class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}

public static void main(String[] args) throws Exception {
Animal cat=new Cat();
// cat.eat();
// cat.work();
cat.sleep();//此处编译会报错。
}
}

在 Java 中,虚方法表(vtable) 是实现多态的核心机制。结合虚方法表,我们可以更清晰地理解为什么 cat.sleep() 会编译报错,以及多态是如何工作的。


1. 虚方法表的概念

  • 虚方法表是每个类在 JVM 方法区中维护的一张表,记录了该类的所有虚方法(可被重写的方法)的实际入口地址。
  • 非虚方法(如 staticprivatefinal 方法)不会出现在虚方法表中,它们的调用在编译时直接绑定。

2. 类的虚方法表结构

以下示例中类的虚方法表如下:

Animal 类的虚方法表

1
2
3
Animal 的虚方法表:
1. eat() -> Animal.eat()
2. work() -> Animal.work()

Cat 类的虚方法表

1
2
3
4
Cat 的虚方法表:
1. eat() -> Cat.eat() // 重写父类的 eat()
2. work() -> Animal.work() // 继承父类的 work()
3. sleep() -> Cat.sleep() // 新增的方法

Dog 类的虚方法表

1
2
3
Dog 的虚方法表:
1. eat() -> Dog.eat() // 重写父类的 eat()
2. work() -> Animal.work() // 继承父类的 work()

3. 多态调用的核心规则

  • 编译时检查:基于引用类型(Animal)的虚方法表。
  • 运行时绑定:基于实际对象类型(Cat)的虚方法表。

4. 问题分析:为什么 cat.sleep() 编译报错?

代码片段

1
2
Animal cat = new Cat();
cat.sleep(); // 编译报错

关键原因

  1. 编译时检查

    • 变量 cat 的编译时类型是 Animal
    • 编译器会检查 Animal 类的虚方法表,发现其中没有 sleep() 方法。
    • 因此直接报错:Animal 类型没有 sleep() 方法
  2. 运行时绑定

    • 虽然实际对象是 Cat,且 Cat 的虚方法表中有 sleep() 方法。
    • 但编译时的检查已经失败,代码无法进入运行阶段。

5. 虚方法表的运行时行为

如果通过强制类型转换调用 sleep()

1
((Cat) cat).sleep(); // 编译通过,运行时调用 Cat.sleep()

运行时步骤:

  1. 强制转换:告诉编译器 cat 实际是 Cat 类型。
  2. 编译检查:检查 Cat 的虚方法表,发现 sleep() 方法存在。
  3. 运行时绑定:通过 Cat 的虚方法表找到 sleep() 的入口地址,执行具体实现。

6. 多态的核心总结

阶段 规则
编译时 基于引用类型的虚方法表检查方法是否存在。
运行时 基于实际对象类型的虚方法表动态绑定方法实现。
  • 父类引用无法调用子类新增方法:因为父类的虚方法表中没有该方法。
  • 重写方法可以动态绑定:因为子类的虚方法表会覆盖父类方法的入口地址。

7. 最终结论

cat.sleep() 编译报错的根本原因是:编译时检查基于 Animal 的虚方法表,而 Animal 的虚方法表中没有 sleep() 方法。虚方法表机制确保了多态的安全性和灵活性,但编译时的类型检查仍然是严格基于引用类型的。