11-代码的执行顺序

一、代码执行顺序

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()调用父类构造函数进行父类初始化(显示初始化、构造初始化)。父类初始化完毕后,子类进行显示初始化,构造代码块初始化,构造函数初始化


猜你喜欢

转载自blog.csdn.net/ruyu00/article/details/79670356