mirror of
https://github.com/CyC2018/Interview-Notebook.git
synced 2026-10-30 07:33:45 +00:00
Update 面向对象思想.md
补充多态的实现:invokevirtual 与动态分派、虚方法表、静态分派和各类调用指令,新增附录 A1。
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user