我在跟着[张龙老师]学习JVM 之 类加载器准备阶段与初始化阶段的重要意义
开头语录:
我写的这些博客只能算是上课笔记,并没有看到哪里有什么原创和非原创的勾选设定,我的标题也阐明了我这类博客的意义。我这人不屑于抄别人的博客,也不屑于照着书或视频原封不动的搬过来。我觉得这样的意义不大,学到了就是学到了,即便博客再多,几十上百个有什么用?
讲述这个知识前,我不得不佩服张龙老师,这个章节的知识让我重新对java 或 JVM 有了更深层的认识。
视频中所有的案例,我也会亲自动手实验,老师并不会把每个知识点都讲明白,所以有时候自己再做练习的时候遇到感到疑惑的问题也会记录在博客中。我不保证结果是否真的是对的,描述是否正确,若有博客内容有问题,请下方留言(一起学习,一起进步)。
人丑话不多,进入主题....
准备阶段:
准备阶段位于类连接中第二个阶段(验证、准备、解析),它主要作用是用于将类中静态变量进行设定默认值(所谓默认值就是这个类型的基本值,如:int =0,boolean=false,引用类型=null等等)。
初始化阶段:
初始化阶段又是位于类加载的第三个阶段(加载、连接、初始化),上面所说准备阶段是将类中的静态变量设定为默认值,那么在初始化阶段,那么就为这些变量初始化为实际的值。
案例一(代码):
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static String name="张三"; }
案例一(结果):
张三
Process finished with exit code 0
结合案例一来说:再准备阶段name等于null,因为String属于引用类型,其默认值就是为null(这个不用做过多强调)。然后我们从结果中看到打印的name是“张三”。值得注意的是这里的“张三”,是在初始化阶段进行赋值的,所以当我们执行 TestA.name 结果就等于 “张三”。
了解了 准备阶段 于 初始化阶段,两则的区别及执行顺序,那么在深入的了解 准备阶段 和 初始化阶段。就拿张龙老师给的案例来说明(若自己能把问题、流程等阐述清楚,那么我就真的搞懂了)。
案例二(代码):
描述:定义两个int类型的静态变量(number1、number2),及一个单例,并在构造方法进行两个变量进行 i++操作,最后在静态方法中依次打印两个变量的值。
其实这里(只是指案例二)用不用单例都是无所谓的,只是想调用静态方法(至于为什么下面说)
调用 静态方法 getInstance() 获取结果。
public class Test1 { public static void main(String[] args) { TestA.getInstance(); } } class TestA { private static int number1=0; private static int number2; public static TestA testA=new TestA(); private TestA(){ number1++; number2++; } public static TestA getInstance(){ System.out.println("number1 = "+number1); System.out.println("number2 = "+number2); return testA; } }
案例二(结果):
这个案例很简单,代码依次从上往下执行(初始化的初始化,赋值的赋值),所以两个变量结果都为 1。
number1 = 1 number2 = 1 Process finished with exit code 0
案例三(代码):
稍微改写一下代码注意红色代码位置,
public class Test1 { public static void main(String[] args) { TestA.getInstance(); } } class TestA { private static int number2; public static TestA testA=new TestA(); private TestA(){ number1++; number2++; } private static int number1=0; public static TestA getInstance(){ System.out.println("number1 = "+number1); System.out.println("number2 = "+number2); return testA; } }
案例三(结果):
number1 的结果却为0(刚开始看到这个结果时,我很惊讶)
number1 = 0
number2 = 1
Process finished with exit code 0
让我讲诉这个问题,可能并没有张龙老师讲得那么清楚,但是肯定能讲明白(讲到讲不明白,何谈自己明白了?)。
最开始就讲了 准备阶段和初始化阶段,他们的流程及关系。只要明白了这两个,案例三的代码及最终结果就明白了(没有这种(大佬)思维,以为懂了就懂了,所以对案例三的结果感到惊讶)。
那么先看看 案例三在最终结果之前做些什么事(这里按照步骤的方式讲解)
- 准备阶段,首先将静态变量从上往下 设置成默认值 ,那么这时number2=0、testA=null、number1=0。
- 当调用一个类静态变量或静态方法会导致该类的出全部初始化,并设置成实际的默认值。
- 那么这时number2依然为0,testa要赋予实际的值(创建 TestA() 对象),执行TestA的构造函数,函数体里依次对number1、number2进行 i++操作。
private TestA(){ number1++; number2++; }
- 代码从上往下执行,执行到构造方法这里时,number1依就时默认值,并没有初始化实际的值(也就是0)。但是number2已经完成了初始化(虽然还是0)。
- testA 执行完成后,就该对 number1 初始化实际值的了。number1实际值是0,所以初始化后 number1还是为0,将原来的1给覆盖了。
- 所以最终结果 number1=0、number2=1。
这里也许还不明白,因为我觉得将 number1 改为其他值会好一点,0011容易绕进去。
案例四(代码):
将 number1=10 ,看代码是否知道最终结果?
public class Test1 { public static void main(String[] args) { TestA.getInstance(); } } class TestA { private static int number2; public static TestA testA=new TestA(); private TestA(){ number1++; number2++; } private static int number1=10; public static TestA getInstance(){ System.out.println("number1 = "+number1); System.out.println("number2 = "+number2); return testA; } }
案例四(结果):
这个结果和你想得是否一致呢?
number1 = 10
number2 = 1
这样的结果,和案例三流程是一样的,案例三是最后初始值是0,这里只是让你不要被默认值和初始值给带进去。
若已经明白了,在看看下面这个案例,是否可以推敲出来
案例五(代码):
这里将number1不设置任何默认值,能否知道结果?
public class Test1 { public static void main(String[] args) { TestA.getInstance(); } } class TestA { private static int number2; public static TestA testA=new TestA(); private TestA(){ number1++; number2++; } private static int number1; public static TestA getInstance(){ System.out.println("number1 = "+number1); System.out.println("number2 = "+number2); return testA; } }
案例五(结果):
number1 = 1 number2 = 1 Process finished with exit code 0
number1在准备阶段定义成默认值(0)后,初始化阶段中,在构造方法中对numbe1进行i++操作,值从0更改为1。当执行到 private static int number1; 时并没有实际的值默认值赋予 number1,所以number1 得依旧为1。
笔后语录:
不知道我是否将明白,若你还不明白,就请你去看张龙老师的教学视频,若我讲的过程有什么错误,请下方留言,我会及时验证及更正。还有就是其实可以不用单例(也是我这里想到的),我用 单例做了什么吗?其实什么也没有,所以说往往不敢确定答案之前,就不敢做大胆的尝试。你只有尝试过后,才会明白,哦~原来如此。