think in java 我对上次初始化和类加载中的例子的实验

其实就是java中类加载时初始化的顺序

只是从代码层面进行非常简略的解读,没有深入到jvm哦。

 

上次有个例子是这样的:

运行结果:

其实我觉得这个过程看得还是不太直白,我另外写几个例子进行验证类加载的过程。

1.首先要明白,这里有概念如下:

1、虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化。

2、只有在调用new方法时才会创建类的实例。

3、类实例的创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法。

4、类实例销毁的时候,首先销毁子类部分,再销毁父类部分。(这一点暂时不验证,因为要不停创建对象来吸引GC销毁堆中的对象,才能通过finalize()方法看出来,我不想弄得太复杂了(其实就是懒))

2.接下来验证一下:

parent.java

运行结果如下:

这里是直接调用static方法的情况,并没有创建对象。

我的理解:

1.首先,依次输出了“static块中的父类的静态变量”和“父类静态代码块”。说明加载过程中初始化了parent类中的static块,因为字段parent1不为null,说明这个字段先于static块中被初始化。

2.然后,依次输出了“method1中的父类的静态变量”和“父类静态方法1”。说明method1方法此时被调用,而且字段parent1此时已经存在。因为输出顺序的缘故,method1方法在初始化static代码块之后执行。

3.最后,我们发现没有构造函数执行。说明这个方法调用的过程没有真正生成对象,只是到执行了方法就结束了。(因为static方法是可以直接在类中调用的)

static修饰的方法是类的方法,不需要创建对象就可以调用,而非静态方法,只有对象被创建了,才可以调用方法。

直接调用static方法,步骤如下:

先初始化方法中的静态成员变量,然后初始化static块,然后调用方法,之后直接结束,没有创建对象。

3.做对比,改写一下main方法,真正创建一个parent对象:

其实就是从“直接调用方法”改成了“新建一个对象并调用其方法”

运行结果如下:

输出结果变多了,因为是真正创建一个Parent对象,所以初始化步骤也变多了。

我的理解:

1.首先,输出了“static块中的父类的静态变量”和“父类静态代码块”。和上个例子一样,先初始化了字段parent1,然后初始化了static块。

2.其次,输出了“父类非静态代码块”。说明此时初始化了非静态代码块。

这里有我的理解,不知道是否正确,可能需要回头再校对。

到了这一步,类加载已经完成了。

静态成员都已经加载完了,已经在内存中分配了空间。此时,必要的类已全部装载完毕(类加载完成了),所以能够创建对象。首先,这个对象中的所有基本数据类型都会设成它们的默认值,而将对象句柄设为null。随后会调用基础类构建器。在这种情况下,调用是自动进行的。但也完全可以用super来自行指定构建器调用。

下面的步骤是在类初始化过程中完成的。

4.然后,输出了“构造方法中的父类的静态变量”和“父类的构造方法”。说明此时执行了构造方法,而且字段变量是先于构造方法初始化的。

5.之后,输出了“method1中的父类的静态变量”和“父类静态方法1”。说明此时执行了method1方法。

那么问题来了,这里的static方法是什么时候进行初始化的呢?

后来一想才知道,虽然不被调用就不会执行,但是静态方法也属于静态成员变量,和静态变量一样,在类加载的时候就会进行初始化了。

新建对象,步骤如下:

先执行类加载。类加载过程中初始化static部分,类加载完成后,进行对象的创建,初始化所有的非static部分,之后完成创建。

4.接下来就引入继承

引入了继承,对象的创建过程就有变化了。主要体现在类加载顺序和对象初始化顺序上。

改写程序:

parent.java作为父类

son.java作为子类

创建一个新的son对象,运行结果如下:

我的理解:

1.首先,输出“父类的静态代码块”。说明先初始化父类中的静态代码块。如果父类依然有父类,那么以此类推。

2.其次,输出“子类静态代码块”。说明这时候初始化了子类中的静态代码块。千万不要想当然,java中的类不是顺序加载的。很多人可能认为:在子类加载过程中发现了继承了父类,那么先完成父类中的类加载,再来完成子类中的类加载。事实上,think in java中提到:

若基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。接下来,会在根基础类执行static初始化,再在下一个衍生类执行,以此类推。保证这个顺序是非常关键的,因为衍生类的初始化可能要依赖于对基础类成员的正确初始化。

可以看出,是先加载所有基础类,然后从跟基础类开始逐一执行static的初始化。

这里有我的理解,不知道是否正确,可能需要回头再校对。

因为static的初始化是类加载的步骤中执行的,当所有static初始化完毕,代表所有static部分都正确分配了内存,所有类加载已经结束了。这时候可以开始进行对象的创建(初始化)。没有static修饰的实例变量,只有对象被创建了,才会分配内存空间,每一个对象的实例变量互不相关。

3.然后,输出了“父类非静态代码块”和“父类的构造方法”。因为使用了构造函数,所以此时parent对象已经被创建了。

4.之后,输出了“子类非静态代码块”和“子类的构造方法”。同上,此时son对象已经被创建了。

引入继承的新建对象,步骤如下:

先执行类加载,类加载过程中发现继承了父类,于是加载父类,直至加载到根基础类为止。类加载的过程中进行static成员变量和static代码块的初始化。顺序是:先初始化父类的static成员变量,再初始化父类的static代码块,再初始化子类的static成员变量,再初始化子类的static代码块。这时候,所有类都加载完成了,开始初始化对象。先初始化父类非静态成员变量,再初始化父类非静态代码块,再使用父类构造函数。接下来是初始化子类非静态成员变量,再初始化子类非静态代码块,再使用子类构造函数…以此类推,直至完成所有对象的创建。

总结步骤如下:

父类静态成员变量->父类静态代码块->子类静态成员变量->子类静态代码块->父类非静态成员变量->父类非静态代码块->父类构造函数->子类非静态成员变量->子类非静态代码块->子类构造函数

5.总结

这次从代码层面研究了类加载和初始化的的过程。如果之后感觉有问题,我会赶紧回来修改。

发表评论

电子邮件地址不会被公开。 必填项已用*标注