在运行时根据对象的类型进行绑定称为后期绑定(动态绑定),这是实现多态的前提。
由多态引申出一个对象初始化具体步骤的问题,自己想了一个模型:
class Shape {
int a;
double b;
Shape(){f1();}
Shape(){f1();}
void f1(){println("shape.f1");}
private void f2(){println("shape.f2");}
}
public class Circle extends Shape {
void f1(){println("circle.f1");} //override
void f2(){println("circle.f2");}
public static void main(String[] args){
Shape s = new Circle();
s.f1();
s.f2();
}
}
output:
circle.f1
circle.f1
circle.f1
shape.f2
将派生类对象向上转型为基类,根据实际对象调用重载的方法,是多态的特性。开始我很疑惑,怎么可以在基类的构造器调用派生类的重载函数?在内存中,对象的具体实现是怎样的呢?就是以怎样的形式存在于内存空间中的呢?这就要涉及对象的初始化步骤:
2.加载并找出该类的编译代码(.class文件中)
3.加载过程中若存在基类(由extends可知)则先加载基类编译代码
4.根基类的static初始化,接着是导出类的static块
5.创建对象:所有类成员变量初始化(或置0);从根基类开始按顺序调用构造函数
6.实例变量按顺序初始化
关于成员函数在类中的表示方式,和师兄讨论过之后恍如大悟。他是站在C++的角度解释这个问题,但是我觉得很有道理。
首先,要清楚,函数并无“实例”的概念。程序被载入后,函数是在内存中的代码区而不是在数据区。函数不会被实例化,类在初始化生成this指针的时候,并不将任何函数装入内存,装入内存的是函数入口的偏移指针,这个入口指针可以一早确定并赋值给代表virtual function的类成员变量。
也就是说,s对象初始化的时候,f1(),f2()作为类成员变量(实际上是“指针变量”),其实存储的是对应函数的入口地址,而非函数代码本身。override之后,f1()接口处存放的就是派生类的函数入口,而非基类的函数入口。f2()由于定义为final,则其内容无法被修改,故依然指向基类的函数入口。
从上面的例子来看,circle从shape类继承来的是一个int,一个double,一个"指针变量",和一个"常指针";对象初始化的时候覆盖了f1()指针变量地址,然后创建了另一个指针变量f2()(扩展接口),所以就可以通过基类的构造器调用派生类的函数了(步骤5)。
學習了
回复删除