一、代码执行顺序
1、编译器编译完之后,.class 文件中就包含构造函数了。且静态成员在编译生成 .class 文件时,就已经有所属的类了(静态成员随着类的加载而存在)
2、代码执行顺序:
(1)当执行 java xxx 命令时,类加载进内存,开辟空间。方法区中,构造函数等非静态方法加载进方法区,静态成员加载进方法区的静态区。以上加载顺序按照代码的执行顺序。方法区中存储的是方法的代码,无论是静态的还是非静态的方法,在方法区中都是共享的
注:方法区的静态区中,除了存储静态方法,还存储静态变量。静态变量进入方法区的静态区后,先默认初始化,后进行指定初始化(包括在堆中创建对象,并将地址值赋给静态变量 -- 单例模式)
(2)方法加载进方法区后,JVM调用main函数执行,main函数进栈
注:方法执行需要进栈。栈是运行区,方法进栈是因为局部变量存在栈中。方法进栈,如果有局部变量,则在对应的方法中指明。执行方法时,实际执行的代码还是方法区中方法的代码
(3)在main函数中,使用到哪个类,哪个类就开辟空间,加载进内存 -- 在方法区中加载方法。如果是子父类,父类先于子类加载。且子类加载进内存时,方法区中子类的方法就持有一个super引用指向父类空间
注:main函数是静态方法,想要在main中调用非静态的方法,只能创建对象,用对象调用
(4)new时,就在堆内存中开辟空间,分配地址值,进行默认初始化。如果是子父类,子类创建对象后,父类先于子类加载进子类对象,分空间存储,分别初始化。父类的成员变量持有super,子类的成员变量持有this。然后构造函数进栈,执行构造函数中的方法,进行构造函数初始化,有参数就进行参数值的传递(不调用构造函数,对象初始化不成功)。之后构造函数弹栈,并将地址值赋给栈中对应的局部变量,局部变量指向堆中对象
注:静态变量在方法区的静态区中,成员变量在堆内存的对象中,局部变量在栈中
如果调用静态变量,无需进栈,直接在方法区的静态区中被使用。如果调用方法(静态和非静态方法),都需要进栈
3、子父类中子类对象的实例化过程:
(1)new Xxx()时,JVM读取指定路径(classpath)下的Xxx.class文件,加载进内存。如果有父类,会先加载父类再加载子类(方法区中,先加载父类的方法,再加载子类的方法,且子类的方法持有super指向父类空间)
(2)在堆内存中开辟空间,分配地址值,进行默认初始化
(3)执行子类构造函数的第一句super(),转去执行父类的显示初始化、构造函数
(4)父类初始化完毕后,执行子类成员变量的显示初始化,之后执行子类的构造函数
eg1:
class Fu {
Fu() {
//super();
show();
//return;
}
void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
int num = 8;
Zi() {
//super();
System.out.println("Zi constructor run ... " + num);
num = 10;
//return;
}
@Override
void show() {
System.out.println("Zi show ... " + num);
}
}
class ExtendsDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
结果:
Zi show ... 0
Zi constructor run ...8
Zi show ... 10
说明:new Zi()时,在堆中开辟空间,分配地址值,进行默认初始化,此时num=0。之后构造函数进栈,进行子类初始化。执行构造函数第一句super()时,调用父类构造函数,父类构造函数中调用show()方法,因为子父类持有的this都是子类堆中对象的地址值,所以执行的是子类的show()方法。父类构造函数执行完毕弹栈,进行子类成员变量显示初始化,此时num=8。之后继续执行子类的构造函数,进行构造函数初始化,num=10
eg2:
class Fu {
Fu() {
//super();
System.out.println("Fu constructor run");
show();
//return;
}
void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
int num = 8;
{
System.out.println("constructor ... " + num);
num = 9;
}
Zi() {
//super();
System.out.println("Zi constructor run ... " + num);
num = 10;
//return;
}
@Override
void show() {
System.out.println("Zi show ... " + num);
}
}
class ExtendsDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
结果:
Fu constructor run
Zi show ... 0
Zi constructor ... 8
Zi constructor run ... 9
Zi show ... 10
说明:new Zi() 时,子类构造函数先加载进内存,分配空间,进行默认初始化。之后执行子类构造函数,在构造函数的第一行,通过super()调用父类构造函数进行父类初始化(显示初始化、构造初始化)。父类初始化完毕后,子类进行显示初始化,构造代码块初始化,构造函数初始化