Update 面向对象思想.md

补充多态的实现:invokevirtual 与动态分派、虚方法表、静态分派和各类调用指令,新增附录 A1。
This commit is contained in:
CyC2018
2026-10-28 22:51:26 +08:00
parent e89d0f6752
commit 0cc27eccd4
+52
View File
@@ -15,6 +15,7 @@
* [三、设计原则](#三设计原则)
* [S.O.L.I.D](#solid)
* [其他常见原则](#其他常见原则)
* [附录:实测](#附录实测)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
@@ -135,6 +136,25 @@ Wind is playing...
Percussion is playing...
```
编译器并不知道 instrument 实际指向哪个类,循环中的 instrument.play() 被编译成一条 invokevirtual 指令,参数是符号引用 Instrument.play:()V(javap 输出,见[附录 A1](#a1-方法调用的字节码))。运行时 JVM 才根据对象的实际类型选择方法:从对象所属的类开始,沿继承链向上找第一个签名相同的方法(JVM 规范 invokevirtual 一节)。这个过程称为动态分派。
每次调用都沿继承链查找太慢,HotSpot 为每个类维护一张虚方法表(vtable),按固定的槽位存放可被覆盖的方法的入口:子类的表先复制父类的表,覆盖了的方法替换对应槽位的入口,新增的方法追加在后面。play() 在 Instrument、Wind、Percussion 三张表中是同一个槽位,分别指向三个类自己的实现,调用时按槽位取出入口即可。通过接口调用的方法使用 invokeinterface,HotSpot 为它另外维护接口方法表(itable)。
重载则在编译时就确定了:编译器根据参数的静态类型选择重载的版本,写进字节码的符号引用中,称为静态分派。例如:
```java
static void show(Instrument i) { System.out.println("show(Instrument)"); }
static void show(Wind w) { System.out.println("show(Wind)"); }
Instrument i = new Wind();
show(i); // show(Instrument)
show((Wind) i); // show(Wind)
```
i 实际指向 Wind 对象,但它的静态类型是 Instrument,第一次调用编译成 show:(LInstrument;)V,运行时不会再改变。
不能被覆盖的方法只有一个实现,调用目标在编译时就能确定:静态方法用 invokestatic,构造方法用 invokespecial;private 和 final 方法虽然也用 invokevirtual 调用(JDK 21 javac 的输出),JVM 同样能直接确定调用目标。
## 二、类图
每张类图后附有对应的 [PlantUML](https://www.planttext.com/) 代码,更多语法及使用请参考:http://plantuml.com/ 。
@@ -362,9 +382,41 @@ Vehicle .. N
包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。
## 附录:实测
### A1 方法调用的字节码
环境:JDK 21.0.13。在正文 Music 类的基础上加入两个重载的 show() 方法、一个 private 方法和一个 final 方法,运行后用 javap -c 查看 main() 中的调用指令(省略了 println 等无关的行):
```java
Instrument i = new Wind();
show(i);
show((Wind) i);
Music m = new Music();
m.secret(); // private void secret()
m.fixed(); // public final void fixed()
```
```text
Wind is playing...
Percussion is playing...
show(Instrument)
show(Wind)
63: invokevirtual #54 // Method Instrument.play:()V
73: invokespecial #28 // Method Wind."<init>":()V
78: invokestatic #57 // Method show:(LInstrument;)V
85: invokestatic #63 // Method show:(LWind;)V
92: invokespecial #66 // Method "<init>":()V
97: invokevirtual #67 // Method secret:()V
101: invokevirtual #70 // Method fixed:()V
```
## 参考资料
- Java 编程思想
- [The Java Virtual Machine Specification, Java SE 21 Edition:invokevirtual](https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-6.html#jvms-6.5.invokevirtual)
- 周志明. 深入理解 Java 虚拟机:第 3 版 [M]. 机械工业出版社, 2019. 第 8 章“方法调用”。
- 敏捷软件开发:原则、模式与实践
- [面向对象设计的 SOLID 原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html)
- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)