对象加载的流程
-
加载Demo.class文件进方法区,并进行空间分配。
-
如果有静态变量,先默认初始化,显示初始化。
-
如果有静态代码块,要执行,仅执行一次。
-
main方法进栈,通过new在堆中开辟空间,并明确首地址。
-
对对象的属性进行默认初始化。
-
调用相应的构造函数进行初始化。
-
构造函数内部。
7.1 调用父类的构造函数super()。
7.2 成员变量的显示初始化。
7.3 构造代码块初始化。
7.4 构造函数内容自定义初始化。 -
对象初始化完毕,将地址赋值给引用变量。
代码实现
先对父类进行测试
public class father {
@Override
public String toString() {
return "父亲的 [age=" + age + "]";
}
public int age = 18;
public father() {
System.out.println("父亲的构造方法");
}
static {
System.out.println("父亲的static代码块");
}
{
System.out.println("父亲构造代码块");
}
}
测试代码
import com.Demo.StaticDemo;
import com.Demo.father;
import com.Demo.son;
import com.Demo.yeye;
public class statictest {
public static void main(String[] args) {
father f=new father();
System.out.println(f);
//StaticDemo.print();
//son s=new son();
//s.haha();
//yeye y=new yeye();
//System.out.println(y.age);
//System.out.println(y.sex);
//System.out.println(yeye.sex);
}
}
测试结果
!!! 注意,静态代码块,构造代码块,和构造函数,他们的执行顺序和写的时候的顺序没关系,也就是说书写的顺序不影响这三个的执行顺序。
对子类和父类进行测验
package com.Demo;
public class son extends father {
static {
System.out.println("儿子的static代码块");
}
public son() {
super();
System.out.println("儿子的构造方法");
}
public void haha() {
System.out.println("年龄"+age);
}
{
System.out.println("儿子构造代码块");
}
}
测试代码
import com.Demo.StaticDemo;
import com.Demo.father;
import com.Demo.son;
import com.Demo.yeye;
public class statictest {
public static void main(String[] args) {
//father f=new father();
//System.out.println(f);
//StaticDemo.print();
son s=new son();
s.haha();
//yeye y=new yeye();
//System.out.println(y.age);
//System.out.println(y.sex);
//System.out.println(yeye.sex);
}
}
测试结果
再来个复杂的我们看一下。
public class OOPDemoE {
public static void main(String[] args) {
Son son=new Son();
son.show();
son.test();
}
}
class Father{
int haha;
Father(){
haha=100;
show();//调用的是子类的show 因为重写
}
void show(){
System.out.println("Fatehr show"+haha);
}
}
class Son extends Father{
int xixi=50;
Son(){
super();//父类的构造函数一旦执行完毕 立马显式初始化
show();
test();
xixi=200;
show();
test();
}
void show(){
System.out.println("Son show"+haha+":"+xixi);
}
void test(){
System.out.println("Son test"+haha+":"+xixi);
}
}
思考一下它的执行顺序是什么?
补充:
JAVA初始化顺序:
1.初始化静态属性,即静态代码和静态变量。按照先父类再子类的顺序初始化。
2.初始化类属性,即成员变量、代码块和构造函数。其中类变量和代码块优先级相同且均优先于构造函数。也是按照先初始化父类再子类的顺序。
我们可以看到,类的初始化分为两大部分:即静态属性和非静态属性。静态属性优先于非静态属性。
优先级层次(相同优先级的属性按程序先后顺序初始化):
第一级:静态属性
1. 父类: 静态成员变量=静态代码块
2. 子类: 静态成员变量=静态代码块
第二级:非静态属性
1. 父类:
1.1 成员变量 = 代码块
1.2 构造函数
2. 子类:
2.1 成员变量 = 代码块
2.2 构造函数
package com.company;
class Father {
public String str1 = getString("父类public变量"); //类变量
public static String str2 = getString("父类静态变量"); //静态变量
private String str3 = getString("父类private变量"); //类变量
//类代码块
{
System.out.println("父类代码块");
}
//静态代码块
static {
System.out.println("父类静态代码块");
}
//father类构造函数
Father() {
System.out.println("父类构造函数");
}
//输出变量数值以作测试
public static String getString(String i){
System.out.println(i);
return i;
}
}
class Child extends Father{
//father类构造函数
Child() {
System.out.println("Child类构造函数");
}
//类代码块
{
System.out.println("子类代码块");
}
//静态代码块
static {
System.out.println("子类静态代码块");
}
private String str6 = getString("子类private变量"); //类变量
public static String str5 = getString("子类静态变量"); //静态变量
public String str4 = getString("子类public变量"); //类变量
}
public class Demo {
public static void main(String[] args) {
new Child();
}
}
测试结果:
结果分析:
在子类的代码中,我们故意将静态代码块、静态变量、成员变量,代码块和构造函数顺序调整。可以看到程序的执行结果始终严格按照上面所列的优先级执行。
/延伸*/
为什么会子类的静态代码出现在父类的非静态属性之前呢,这里涉及到Java的装载和实例化。
类的加载和实例化顺序:
1) 在堆内存中生成class对象, 把静态变量和静态方法加载到方法区, 这个堆内存中的class对象是方法区数据的入口
2) 静态变量默认初始化
3) 静态变量显式初始化
4) 执行静态代码块
5) 成员变量默认初始化, 显示初始化
6) 执行构造函数
其中有涉及父子关系的类的装载中,先父类静态变量和代码块初始化,再子类。然后实例化时,父类成员变量和代码块初始化, 执行父类构造函数, 子类成员变量和代码块初始化, 执行子类构造函数。