Java Design Philosophy – Polymorphism

java.lang.Object

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.

Java SE 17 API Documentation: java.lang.Object

Object 类是类层次结构的根。每个类都以 Object 作为超类。所有对象(包括数组)都实现了该类的方法。

方法 作用
equals(Object obj) 判断两个对象在逻辑上是否“相等”
hashCode() 返回对象的哈希码,用于哈希表(如 HashMap
toString() 返回对象的字符串表示,便于调试和日志
getClass() 获取运行时类信息,是反射的基础
clone() 创建对象副本(需实现 Cloneable 接口)
wait() / notify() / notifyAll() 支持线程间协作与同步
finalize() 已废弃,曾用于对象销毁前的资源清理
Modifier and Type Method Description
protected Object clone() Creates and returns a copy of this object.
boolean equals(Object obj) Indicates whether some other object is “equal to” this one.
protected void finalize() Deprecated. The finalization mechanism is inherently problematic.
final Class<?> getClass() Returns the runtime class of this Object.
int hashCode() Returns a hash code value for the object.
final void notify() Wakes up a single thread that is waiting on this object’s monitor.
final void notifyAll() Wakes up all threads that are waiting on this object’s monitor.
String toString() Returns a string representation of the object.
final void wait() Causes the current thread to wait until it is awakened, typically by being notified or interrupted.
final void wait(long timeoutMillis) Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.
final void wait(long timeoutMillis, int nanos) Causes the current thread to wait until it is awakened, typically by being notified or interrupted, or until a certain amount of real time has elapsed.

这些方法构成了 Java 对象模型的最小公共接口。无论你定义的是 Person 或者 Animal ,他们都支持这些操作。在面向对象语言中,若所有对象都具备某些共性行为,那么就应通过一个统一的根类来表达这种共性,避免重复定义。

多态 是面向对象的三大特性之一(封装、继承、多态),其核心含义是:

同一个方法调用,在不同对象上表现出不同的行为

在 Java 中,这通常通过 父类引用指向子类对象,并 重写(override)方法 来实现。

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
37
38
39
public class Father {

public static int testStaticMethod()
{
return 1;
}

public void testMethod()
{
System.out.println("father");
}

public static void main(String[] args) {
Father father = new Father();
Father fatherSon = new Son();
Son son = new Son();

father.testMethod();
fatherSon.testMethod();
son.testMethod();

int i = testStaticMethod();
}

}

class Son extends Father{
public static int testStaticMethod()
{
return 1;
}

@Override
public void testMethod()
{
System.out.println("son");
}
}

以该代码为例,使用以下命令反编译

1
javap -c -verbose Father

我们将得到

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
Classfile /D:/Father.class
Last modified 20251020日; size 838 bytes
SHA-256 checksum ffa2d66d70489db7e8cd156b8d8465bf2168344a28e81acfbac72942d4a55f76
Compiled from "Father.java"
public class Father
minor version: 0
major version: 61
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #21 // Father
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 4, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #10 // java/lang/System
#9 = NameAndType #11:#12 // out:Ljava/io/PrintStream;
#10 = Utf8 java/lang/System
#11 = Utf8 out
#12 = Utf8 Ljava/io/PrintStream;
#13 = String #14 // father
#14 = Utf8 father
#15 = Methodref #16.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #18 // java/io/PrintStream
#17 = NameAndType #19:#20 // println:(Ljava/lang/String;)V
#18 = Utf8 java/io/PrintStream
#19 = Utf8 println
#20 = Utf8 (Ljava/lang/String;)V
#21 = Class #22 // Father
#22 = Utf8 Father
#23 = Methodref #21.#3 // Father."<init>":()V
#24 = Class #25 // Son
#25 = Utf8 Son
#26 = Methodref #24.#3 // Son."<init>":()V
#27 = Methodref #21.#28 // Father.testMethod:()V
#28 = NameAndType #29:#6 // testMethod:()V
#29 = Utf8 testMethod
#30 = Methodref #24.#28 // Son.testMethod:()V
#31 = Methodref #21.#32 // Father.testStaticMethod:()I
#32 = NameAndType #33:#34 // testStaticMethod:()I
#33 = Utf8 testStaticMethod
#34 = Utf8 ()I
#35 = Utf8 Code
#36 = Utf8 LineNumberTable
#37 = Utf8 LocalVariableTable
#38 = Utf8 this
#39 = Utf8 LFather;
#40 = Utf8 main
#41 = Utf8 ([Ljava/lang/String;)V
#42 = Utf8 args
#43 = Utf8 [Ljava/lang/String;
#44 = Utf8 fatherSon
#45 = Utf8 son
#46 = Utf8 LSon;
#47 = Utf8 i
#48 = Utf8 I
#49 = Utf8 SourceFile
#50 = Utf8 Father.java
{
public Father();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LFather;

public static int testStaticMethod();
descriptor: ()I
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_1
1: ireturn
LineNumberTable:
line 5: 0

public void testMethod();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String father
5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 10: 0
line 11: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LFather;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: new #21 // class Father
3: dup
4: invokespecial #23 // Method "<init>":()V
7: astore_1
8: new #24 // class Son
11: dup
12: invokespecial #26 // Method Son."<init>":()V
15: astore_2
16: new #24 // class Son
19: dup
20: invokespecial #26 // Method Son."<init>":()V
23: astore_3
24: aload_1
25: invokevirtual #27 // Method testMethod:()V
28: aload_2
29: invokevirtual #27 // Method testMethod:()V
32: aload_3
33: invokevirtual #30 // Method Son.testMethod:()V
36: invokestatic #31 // Method testStaticMethod:()I
39: istore 4
41: return
LineNumberTable:
line 14: 0
line 15: 8
line 16: 16
line 18: 24
line 19: 28
line 20: 32
line 22: 36
line 23: 41
LocalVariableTable:
Start Length Slot Name Signature
0 42 0 args [Ljava/lang/String;
8 34 1 father LFather;
16 26 2 fatherSon LFather;
24 18 3 son LSon;
41 1 4 i I
}
SourceFile: "Father.java"

我们观察 main 方法中这三行调用

1
2
3
father.testMethod();      // 字节码: invokevirtual #27 → Father.testMethod:()V
fatherSon.testMethod(); // 字节码: invokevirtual #27 → Father.testMethod:()V
son.testMethod(); // 字节码: invokevirtual #30 → Son.testMethod:()V

尽管 fatherSon 的声明类型是 Father,但字节码中调用的是 invokevirtual #27 而不是 son.testMethod

但是在实际运行的时候,JVM 会根据对象的实际类型去查找并执行 Son 中重写的 testMethod

这就是多态的底层实现机制:动态分配 (Dynamic Dispatch)

invokevirtual是 JVM 用于调用 实例方法 的指令。那么我们再从 src/hotspot/cpu/x86/templateTable_x86_64.cpp入手。

1
2
3
4
5
6
7
void TemplateTable::invokevirtual(int byte_no) {
// 1. 从常量池解析方法引用(解析到目标方法的符号信息)
prepare_invoke(byte_no, rax, rdx, CHECK);

// 2. 执行 invokevirtual 分派逻辑
invokevirtual_helper(rax, rdx);
}

其中 prepare_invoke 会从常量池加载方法符号(如 Father.testMethod:()V),但不绑定具体实现

invokevirtual_helper是关键,该函数会调用 方法解析与分派逻辑src/hotspot/share/interpreter/linkResolver.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void LinkResolver::resolve_virtual_call(
CallInfo& result,
Handle receiver,
const LinkInfo& link_info,
Bytecodes::Code bytecode,
TRAPS
) {
// 1. 获取 receiver 的实际 Klass(即运行时类型)
Klass* recvrKlass = receiver->klass();

// 2. 在 recvrKlass 的 vtable 中查找方法
methodHandle method = recvrKlass->method_at_vtable(
link_info.index() // vtable 索引(由解析阶段确定)
);

// 3. 填充调用信息
result.set_virtual(method, recvrKlass, /* ... */);
}

然后会在vtable(虚方法表)中查找 (src/hotspot/share/oops/instanceKlass.hpp

1
2
3
4
5
6
class InstanceKlass : public Klass {
// ...
Array<Method*>* _methods; // 所有方法
int* _vtable_indices; // 方法在 vtable 中的位置
// vtable 本身在 InstanceKlass 初始化时构建
}

vtable 的结构保证:子类重写的方法会覆盖父类同签名方法的位置

然后根据继承链查找,进行实际调用。

总结来说就是以下步骤

步骤 Java 描述 HotSpot C++ 实现
1. 取 this 从操作数栈弹出引用 解释器 pop oop
2. 获取实际类型 运行时类型 receiver->klass()Klass*
3. 查找方法 虚方法表 recvrKlass->method_at_vtable(index)
4. 继承链查找 仅在解析期确定 vtable 索引 LinkResolver::lookup_method_in_klasses
5. 调用 执行方法 跳转到 Method* 的解释器/JIT 入口

Java Design Philosophy – Polymorphism
http://yumiera.github.io/Polymorphism/
作者
Yumiera
发布于
2025年10月20日
许可协议