自制Java虚拟机(五)实现继承、多态、invokevirtual
本篇文章将研究如何实现面向对象的继承和多态特性,同时实现invokevirtual。
一、实例属性的继承
继承实现了数据与方法的复用。
类属性与实例属性
类属性的修饰符要加上static,是属于类的
类属性只有一份,该类创建的多个对象共享同一份类属性,jvm中由getstatic、putstatic指令操作
实例属性每个对象各自一份,各管各的互不干扰,jvm中由getfield、putfield指令操作
上篇文章在没有考虑没有继承的情况下实现了getfield、putfield指令,本篇文章来对其进行修正。
public、protected与private
不管是父类的实例属性是public、protected还是private,子类继承自父类时,这些属性都会一一继承,只不过private的属性在子类中不能直接访问而已。你看不到它们,但它们确实存在。
现在问题来了:
创建对象时,我们要给从父类(以及父类的父类…)继承过来的实例属性分配内存,并指定索引以便访问,如何安排这些索引?
子类中访问父类的public、protected实例属性时,索引是指向子类常量池的Fieldref类型的结构,而这个结构的class_index是指向当子类的,必须能够正确地解析到定义该属性的类
解决方法:
实例属性的安排。在类的继承链上,自顶向下给每个类的实例属性从小到大依次指定索引,这样就保证每个类的实例属性在该继承链中索引是唯一的。如下图布局1所示:
因此我们在创建对象的时候必须从当前类开始,沿着继承链遍历父类,计算继承而来的属性个数并重新给每个类的实例属性指定索引(之前计算的的索引是在当前类的)。
修正之后创建对象的函数newObject实现可以如下:
Object* newObject(OPENV *env, Class* pclass) {
CONSTANT_Class_info* parent_class_info;
Object *obj;
Class *tmp_class;
int i = 0, total_size = 0;
// step 1: load parent class recursively
tmp_class = pclass;
while (tmp_class->super_class) {
parent_class_info = tmp_class->constant_pool[tmp_class->super_class];
if (NULL == parent_class_info->pclass) {
parent_class_info->pclass = systemLoadClassRecursive(env, (CONSTANT_Utf8_info*)(tmp_class->constant_pool[parent_class_info->name_index]));
tmp_class->parent_class = parent_class_info->pclass;
}
tmp_class = tmp_class->parent_class;
}
// step 2: calculate fields size of parent class recursively, if not calculated yet
if (pclass->parent_fields_size == -1) {
if (pclass->parent_class == NULL) {
pclass->parent_fields_size = 0;
} else {
pclass->parent_fields_size = getClassFieldsSize(pclass->parent_class);
}
for(i=0; ifields_count; i++) {
// 重新计算属性的索引:过滤掉类属性
if (NOT_ACC_STATIC((pclass->fields[i])->access_flags)) {
(pclass->fields[i])->findex += pclass->parent_fields_size;
}
}
}
// 该对象属性的总大小 = 父类实例属性大小 + 本类实例属性大小
total_size = pclass->parent_fields_size + pclass->fields_size;
obj = (Object*)malloc(sizeof(Object) + ((total_size+1)<<2));
obj->fields = (char*)(obj+1);
obj->pclass = pclass;
obj->length = (total_size+1) << 2;
return obj;
}
由于父类可能需要多次用到,我们可以把解析好的父类保存在一个哈希中,用类的全限定名做为索引,如test/Parent,需要引用父类的时候先用哈希表中找,找不到就加载,加载完毕保存在哈希表中,为了方便,在当前类中设置个字段指向父类。
typedef struct _ClassFile{
...
struct _ClassFile *parent_class; // 指向父类
int parent_fields_size;
int fields_size;
} ClassFile;
typedef ClassFile Class;
(java/lang/Object是所有类的终极父类,我们一直往上解析的话最终会涉及到这个类,可以从Java的安装目录的jdk目录下找到src.zip这个包,这个包有jdk的源码,把它解压到指定目录,编译成class文件,到时我们的类加载函数从这里加载就行)