Java深入研究之类的初始化
首先,我们来看下面这样一个例子:
class Singleton { private static Singleton singleton = new Singleton(); public static int counter1; public static int counter2 = 0; private Singleton() { counter1++; counter2++; } public static Singleton getSingletone() { return singleton; } } public class StaticTest { public static void main(String[] args) { Singleton singleton=Singleton.getSingletone(); System.out.println("count1:"+singleton.counter1); System.out.println("count2:"+singleton.counter2); } }
在看这个例子之前请大家现看下面一些理论知识吧!
类型的生命周期:
类型的生命周期分为装载、连接、初始化、实例的生命周期(使用)和卸载。
装载是指把表示类型信息的二进制字节码装入到内存。
连接阶段包括验证、准备和解析,验证包括语法、语义、字节码流和引用验证;准备是给类变量分配内存并赋默认值;解析是把符号引用解析为直接饮用。
初始化阶段是给静态变量赋值,包括静态语句和静态块里边的赋值操作。实例的生命周期阶段就包括类型实例的创建、实例的使用、实例占用内存的回收(gc)。卸载就是把类型信息占用的内存回收掉。
装载、连接和初始化三个阶段的界线在逻辑上是划分的很清晰的,但是在实际的实际过程中,除了开始时间点是跟逻辑上的先后顺序一致,具体的过程却是交叉进行的。
Java程序对类的使用方式可分为两种
–主动使用
–被动使用
所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们
主动使用(六种)
–创建类的实例
–访问某个类或接口的静态变量,或者对该静态变量赋值
–调用类的静态方法
–反射(如Class.forName(“com.mysql.jdbc.Driver”))
–初始化一个类的子类
–Java虚拟机启动时被标明为启动类的类(Java Test)
除了上述六种情形,其他使用Java类的方式都被看作是被动使用,不会导致类的初始化。
有了上面的一些理论基础,现在让我们来看一下上面那个例子吧。
首先在main方法中Singleton.getSingletone(),符合上面主动使用的第三种,调用类的静态方法,那么类就会被初始化。现在我们先看上面红字标注的地方,
类在连接阶段的时候会给类变量也就是静态变量分配内存并赋默认值,这里的默认值指的是系统给定的值(如int默认值为0 Object的默认值是null)。所以在初始化的时候类变量已经有了默认值,所以在初始化的时候按照从上到下的顺序会先执行new Singleton(),这时会调用构造函数count1和count2都变成了1,然后会执行下面的类变量的赋值,因为count1声明的时候就没有初始值所以count1仍然为1,但是count2有初始值0,所以count2原来的值1就会被0覆盖,所以count2变为了0。
这也就是为什么结果输出的是1 和0了。
现在我们稍微变下将上面的类变量换下位置改成如下
public static int counter1; public static int counter2 = 0; private static Singleton singleton = new Singleton();
那么现在的结果是什么呢?相信大家这次应该可以说出正确的答案了吧!是的,这次结果就会变成了1和1。
那么现在我们再看另一种情况先将静态变量的顺序改为原来的顺序
首先将构造函数由原来的private改变成public 然后在main方法中这样使用
Singleton singleton=new Singleton();
这时结果又是多少呢?
最初我一直以为初始化就会调用构造函数,其实不然,只有new的时候才会调用构造函数,所以这时的结果就会在静态变量初始化完后调用构造函数,结果就变成了2和1。
下面有一写关于初始化的介绍(注意理解红字标注的那句话的意思):
Java中的初始化顺序
JAVA类首次装入时,会对静态成员变量或方法进行一次初始化,但方法不被调用是不会执行的,静态成员变量和静态初始化块级别相同,非静态成员变量和非静态初始化块级别相同。
初始化顺序:先初始化父类的静态代码--->初始化子类的静态代码-->
(创建实例时,如果不创建实例,则后面的不执行)初始化父类的非静态代码(变量定义等)--->初始化父类构造函数--->初始化子类非静态代码(变量定义等)--->初始化子类构造函数
类实例创建时候,首先初始化块部分先执行,然后是构造方法;然后从本类继承的子类的初始化块执行,最后是子类的构造方法
类消除时候,首先消除子类部分,再消除父类部分。
相信读到这里,就应该就会明白了吧。
最后我们再看下面一个例子,首先仔细分析,然后再运行查看结果
class insect{ int i=9; int j; insect(){ prt("i= "+i+" j="+j); j=39; } static int x1=prt("static insect x1 initialized"); static int prt(String s){ System.out.println(s); return 47; } } public class Wps extends insect{ int k=prt("wps be initialized"); Wps(){ prt("k="+k); prt("j="+j); } static int x2=prt("static wps x2 initialized"); static int prt(String s){ System.out.println(s); return 63; } public static void main(String[] args){ insect.prt("initialized constructor"); Wps w=new Wps(); } }
转载于:https://www.cnblogs.com/heshan664754022/archive/2013/06/07/3123434.html