当派生的类的一个实例创建堆分配将是这样的(*):
- 标准JVM对象头(与指针Class对象
DerivedClass
)
- 类的实例字段
Object
- 类的实例字段
BaseClass
- 类的实例字段
DerivedClass
所以实际上,如果你忽略DerivedClass
实例字段,这个对象看起来非常类似于BaseClass的一个实例,并且JVM可以引用这个对象,就好像它是一个BaseClass的实例一样,并且没有困难这样做。
类似地,在用于DerivedClass
类对象是一个“虚拟方法表”与:
- 虚拟方法指针
Object
- 虚拟方法指针
BaseClass
- 虚拟方法指针
DerivedClass
JVM通过索引到此表中来查找特定的虚拟调用ic方法,知道hashValue
是方法编号5而printTheGroceryList
是方法编号23.调用方法所需的编号是在类调用类中加载并缓存在方法引用数据中时确定的,因此调用方法是:获取编号,转到实例头指向的Class对象,索引到虚拟方法表中,拉出指针并分支到方法。
但是当你仔细观察,你会看到,例如,该Object
组指向hashValue
方法指针实际上指向一个在BaseClass
(如果BaseClass的覆盖hashValue
)。因此,JVM可以将对象看作是Object
,调用hashValue
,然后无缝地获取BaseClass
(或DerivedClass
,如果它也覆盖该方法)中的方法。 (*)在实践中,实例字段可能会混杂到一定程度,因为来自超类的“对齐”字段可能会在来自子类的字段填充的堆分配中留下间隙。这只是最小化对象大小的一个技巧。
只有一个实例,即一个对象。然而,对象由两部分组成,一部分来自父代,另一部分来自它自己带来的东西。尽管如此,没有任何需要Oracle撰写关于此的文章。 – gd1 2014-10-05 11:30:31
它就像一个洋葱 - 每一层都是一个类,最内层是Object,然后是每个超类,直到你到达“真实”类。 (这是绝大多数面向对象语言的工作原理。) – 2014-10-05 11:57:50