class Ade{
private static Ade a = new Ade();
public static int count1 = 0;
public static int count2;
private Ade(){
count1++;
count2++;
}
public static Ade getInstance(){
return a;
}
}
--------------------------------------------------------------
public class Test{
public static void main(String[] args) {
Ade a = Ade.getInstance();
System.out.println("count1=="+a.count1);
System.out.println("count2=="+a.count2);
}
}
-------------------------------------------------------------------------------------
先以一道小题给大家琢磨一下上面代码执行后将会输出怎样的结果?
如果不了解类加载机制的朋友,可能琢磨了一会儿以后认为 输出: count1==1 count2==1
然而,实际上结果为count1==0 count2==1
为什么会出现这样的结果呢?
让我们来了解一下类加载机制
类加载的整个过程会经历 三大步(五小步)
1.加载
2.连接
2.1验证
2.2准备
2.3解析
3.初始化
首先咱们从第一步 加载 说起, 加载是类加载器把对应的class文件加载到jvm中来,如果想知道详细诸如双亲委派,可点击进入
我们这里要说的是:什么情况一个类会被加载进来呢?(有个小概念提一提,一个类如果被加载进来了不会再被加载)
①创建一个类的对象
②调用一个类的静态属性或静态方法
③加载一个子类之前会先把父类加载进来
④执行一个类的主函数
⑤反射
也就是说,上面题目中的例子:首先执行了Test的main方法,那么Test这个类先会被加载,然后由上至下执行main方法内容
Ade.getInstance(); 调用了Ade这个类的静态方法,那么Ade再被加载进来。
第二步是连接:
在这一步中有3小步,验证:验证做的事情是验证是否合法,那,为什么通过了编译器的验证还需要在进行加载的时候又验证一遍呢?
简单的可以这样理解,编译器生成的class文件,这个文件假如有黑客进行篡改后再放入java执行,那么如果加载的时候不再次验证
那么安全性很难保证
准备:准备阶段会把这个类中所有的静态的属性加载到方法区并给其赋予默认值,int类型的赋0,String类型的赋为null
也就是说上题中 在准备阶段时: Ade a=null; count1 = 0;count2=0
解析:解析阶段是把类、对象、字段进行解析,其实说简单点就是解析出谁对应谁,解析出这个变量对应的是哪个变量
比如一个变量 name,这个name到底是对应的局部变量的呢?还是对应全局变量的呢?
初始化: 初始化会把这个类中所有的静态的代码块从上至下依次执行(这里有个小概念:静态属性的等号的右边其实就是一个静态代码块)
什么意思呢?上题中在初始化时,会先执行 a = new Ade() ,这个时候 去构造方法中把count1和count2进行了++,接着这一行执行完毕后,
接着执行 count1 = 0; 把0的值再重新赋值给count1,而下面的count2并没有被重新赋值。
因此,最后的结果为:count1==0 , count2==1
如果还没搞懂的朋友,可以把下面两行代码调换下顺序
private static Ade a = new Ade();
public static int count1 = 0;
成为:
public static int count1 = 0;
private static Ade a = new Ade();
那么这样的话,结果会是count1和count2都是0,因为这样的话,初始化时count1 = 0;先被执行了,而后再执行 a = new Ade();