static语句的初始化和类的加载过程共同分析:
第一种情况:
public class Test {
public static int counter1;
public static int counter2 = 2;
private static Test test1 = new Test();
private Test(){
System.out.println("count1test1="+ test1.counter1);
System.out.println("count2test2="+ test1.counter2);
counter1++;
counter2++;
System.out.println("count1test3="+ test1.counter1);
System.out.println("count2test4="+ test1.counter2);
}
public static Test getInstace(){
return test1;
}
}
主函数调用:
public class Mytest {
public static void main(String[] args) {
Test test1 = Test.getInstace();
System.out.println("count1="+ test1.counter1);
System.out.println("count2="+ test1.counter2);
}
}
得到的结果:
count1test1=0
count2test2=2
count1test3=1
count2test4=3
count1=1
count2=3
第二种情况:我们将test代码进行修改:
public class Test {
private static Test test1 = new Test();
public static int counter1;
public static int counter2 = 2;
private Test(){
System.out.println("count1test1="+ test1.counter1);
System.out.println("count2test2="+ test1.counter2);
counter1++;
counter2++;
System.out.println("count1test3="+ test1.counter1);
System.out.println("count2test4="+ test1.counter2);
}
public static Test getInstace(){
return test1;
}
}
主函数不变,调用得到的结果:
count1test1=0
count2test2=0
count1test3=1
count2test4=1
count1=1
count2=2
静态变量的的生命语句,以及静态的代码块都被看做是类的初始化语句(执行类加载的初始化过程),java虚拟机会按照初始化语句在类文件的先后顺序来依次执行他们。
比如第一种情况,
执行了count1,给出int类型的默认值是0.
执行了count2,给出赋值的为2.
执行实例化静态test1变量,这个时候执行了构造函数,给count1和count2各自进行了加一操作。
第二种情况:
首先执行实例化静态变量test1操作,执行了构造函数,里面需要给类加载准备阶段的count1和count2默认值都是0,进行加1操作,得到的都是1。实例化语句完成之后。
执行了count1的赋值操作,count1没有初值,所以值不变仍为1。
执行了count2的赋值操作,count2有初值是2,所以count2的值变为了2.
补充类的加载流程
java文件编译后是字节码文件,class文件装载到内存中,这个是有类加载器来完成的。
这个加载过程:加载、连接、验证、准备、解析、初始化。
加载:将编译好的字节码文件在硬盘上加载到内存中,通过类加载器来完成, 具体一点,就是将字节码文件放到了运行时数据区的方法区内,在java1.8之后方法去合并到了堆区中,然后在堆区创建一 个java.lang.class对象,用来封装类在方法去内的数据结构。
连接:将读入到内存中的二进制数据合并到运行环境中去,比如发现类A使用了类B,这个时候我们因为我们编译好的文件是独立 的,加载到内存之后就开始根据调用关系来合并到一起或者相互连接,然后放到了运行时环境中去。这样形成了类和类之间的关联关系。
验证:文件结构,语法,字节码,二进制等的进行验证。
很多验证都是出于安全性的考虑。防止用户的一些恶意的自己编写的class文件。
准备:虚拟机为类的静态变量分配内存,并设置默认的初始值。比如cnout1和count2在上面,是int类型,在准备阶段分配的是int的默认值。
解析:将二进制数据的符号引用替换为直接引用,比如:类A的AA方法使用了类B的BB方法。在类A的二进制数据中包含了对于类B的BB方法的符号引用(由BB方法的全面和相关描述组成),在解析阶段java虚拟机回吧这个符号引用替换为一直指针,直接指向类B的BB方法在方法区内的位置,这个指针就是直接引用。
解析阶段也可以放到初始化之后完成。
初始化:java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。就是将默认值进行替换为初始值。
简单叙述一下,类的加载过程,后面进行详细的解析。如有不正确,指指正。