java的初始化问题,原来以为是知道的,直到遇到一道题,发现自己还有很多不清楚的地方,正好看到两篇非常好的文章,写的很清楚,于是整理了一下。
转自:bolg.csdn.net/macheng365/article/details/6403050
对于JAVA中类的初始化是一个很基础的问题,其中的一些问题也是易被学习者所忽略。当在编写代码的时候碰到时,常被这些问题引发的错误,感觉莫名其妙。而且现在许多大公司的面试题,对于这方面的考查也是屡试不爽。不管基于什么原因,我认为,对于java类中的初始化问题,有必要深入的了解。Java类的初始化,其实就是它在JVM的初始化问题(类加载的问题),对于它在JVM中的初始化是一个相当复杂的问题,是给专家们来探讨的,所以在这里我只是对一些容易忽略的问题,发表一下个人观点:
1,在一个类的内部(不考虑它是另一个类的派生类):很多人认为,类的成员变量是在构造方法调用之后再初始化的,先不考虑这种观点的正确性,先看一下下面的代码:
[java] view plaincopy
1. class Test01...{
2. public Test01(int i)...{
3. System.out.println("Test01 of constractor : " + i);
4. }
5. }
6. public class Test02 ...{
7. private Test01 t1 = new Test01(1);
8. private int n = 10;
9. public Test02()...{
10. System.out.println("Test02 of constructor : " + n);
11. }
12. private Test01 t2 = new Test01(2);
13. public static void main(String[] args) ...{
14. Test02 test = new Test02();
15. }
16. }
17. 输出的结果为:
18. Test01 of constractor : 1
19. Test01 of constractor : 2
20. Test02 of constructor : 10
通过输出,可见当生成Test02的实例test时,它并不是首先调用其构造方法而是先是成员变量的初始化,而且成员的初始化的顺序以成员变量的定义顺序有关,先定义的先初始化,初始化后再调用构造方法。其实成员变量的初始化,在类的所有方法调用之前进行,包括构造方法
当类中有Static 修饰的成员呢?测试下面一段代码:
[java] view plaincopy
1. public class Test03 ...{
2. private int i1 = printCommon();
3. private static int i2 = printStatic();
4.
5. public Test03()...{
6.
7. }
8. public static int printCommon()...{
9. System.out.println("i1 is init!");
10. return 1;
11. }
12. public static int printStatic()...{
13. System.out.println("i2 is init!");
14. return 2;
15. }
16. public static void main(String[] args) ...{
17. Test03 t = new Test03();
18. }
19. }
20.
21. 输出结果为:
22. i2 is init!
23. i1 is init!
可见static的成员比普通的成员变量先初始化。
我们都知道,如果一个类的成员变量没有在定义时,系统会给予系统默认的值,有=号的就直接给予右值,系统在给予初值和=号给予值这2中方式,在执行时间上有先后吗?为了测试,我编写了如下代码:
[java] view plaincopy
1. public class Test04 ...{
2. private static Test04 t1 = new Test04();
3. private static int i1;
4. private static int i2 = 2;
5.
6. public Test04()...{
7. i1++;
8. i2++;
9. }
10.
11. public static void main(String[] args) ...{
12. Test04 t2 = new Test04();
13. System.out.println("t2.i1 = " + t2.i1);
14. System.out.println("t2.i2 = " + t2.i2);
15. }
16. }
17. 我们先预计一下输出,可能有几种答案:2和3,3和3,2和2
18. 执行代码后:
19. t2.i1 = 2
20. t2.i2 = 3
为什么是2和3呢?其实代码的执行顺序是这样的:首先执行给t1,i1,i2分别给予初始值null,0,0,再执行
Test04 t1 =new Test04(),这样i1++,i2++被执行,i1,i2都变为1,执行完毕后接着执行int i1; i1,i2的值仍然是1,1,当执行int i2 = 2时i2被赋予了值,即i1 = 1,i2=2;再执行Test04 t2 = new Test04(),i1,i2再执行++,此时i1 =2,i2 =3,输出i1,i2,结果就是:t2.i1 = 2,t2.i2 = 3。 通过上面的代码我们可以认为系统默认值的给予比通过等号的赋予先执行。
2,一个类还有上层的类,即父类:
当生成一个子类时,大家到知道会调用父类的构造方法。如果子类和父类中都有Static的成员变量呢,其实我们在深入分析一个类的内部初始化后,对于存在父类的类的初始化其实原理都一样,具体以下面的代码为例:
[java] view plaincopy
1. class SuperClass ...{
2. static...{
3. System.out.println("SuperClass of static block");
4. }
5.
6. public SuperClass()...{
7. System.out.println("SuperClass of constracutor");
8. }
9. }
10.
11. public class SubClass extends SuperClass...{
12. static...{
13. System.out.println("SubClass of static block");
14. }
15.
16. public SubClass()...{
17. System.out.println("SubClass of constracutor");
18. }
19.
20. public static void main(String[] args)...{
21. SuperClass t = new SubClass();
22. }
23. }
24. 输出结果:
25. SuperClass of static block
26. SubClass of static block
27. SuperClass of constracutor
28. SubClass of constracutor
可见当父类,和子类有Static时,先初始化Static,再初始化子类的Static,再初始化父类的其他成员变量->父类构造方法->子类其他成员变量->子类的构造方法。
父类上层还有父类时,总是先执行最顶层父类的Static-->派生类Static-->派生类Static-->.......-->子类Static-->顶层父类的其他成员变量-->父类构造方法--> 派生类的其他成员变量 --> 派生类构造方法--> ...............-->子类其他成员变量-->子类构造方法
讨论到继承,就不得提一下多态:
如果父类构造方法的代码中有子类中被重写得方法,当执行这样的语句
SuperClass super = new SubClass();
初始化时调用父类的构造方法,是执行父类的原方法,还是执行子类中被重写的方法呢?
[java] view plaincopy
1. class SuperClass...{
2. public SuperClass()...{
3. System.out.println("SuperClass of constructor");
4. m();
5. }
6. public void m()...{
7. System.out.println("SuperClass.m()");
8. }
9. }
10. public class SubClassTest extends SuperClass ...{
11. private int i = 10;
12. public SubClassTest()...{
13. System.out.println("SubClass of constructor");
14. super.m();
15. m();
16. }
17. public void m()...{
18. System.out.println("SubClass.m(): i = " + i);
19. }
20. public static void main(String[] args)...{
21. SuperClass t = new SubClassTest();
22. }
23. }
24. 可能很多人会认为输出为:
25. SuperClass of constructor
26. SubClass.m(): i = 10
27. SubClass of constructor
28. SuperClass.m()
29. SubClass.m(): i = 10
30. 其实不然!
31. 正确输出为:
32. SuperClass of constructor
33. SubClass.m(): i = 0
34. SubClass of constructor
35. SuperClass.m()
36. SubClass.m(): i = 10
37. 在生成对象时,父类调用的M()方法,不是父类的 M()方法,而时子类中被重写了的M()方法!!并且还出现一个怪异的现象,子类的privte int i 也被父类访问到,这不是和我们说private的成员只能在本类使用的原则相违背了吗?其实我们说的这条原则是编译期间所遵守的,在JAVA程序的编译期间,它只检查语法的合法性,在JAVA的JVM中,即运行期间,不管你声明的什么,对于JVM来说都是透明的,而多态是在运行期间执行的,所以能拿到SubClass的private成员,一点都不奇怪,只是此时还没执行 i = 10,所以在父类的构造方法中调用m()时,系统只能将i赋予系统初值0。
38. 下面是我设计的一道完整的初始化例子,可测试你对类的初始化问题是否完整掌握:
39. 写出程序运行的结果:
40. class A...{
41. private int i = 9;
42. protected static int j;
43. static...{
44. System.out.println("-- Load First SuperClass of static block start!-- ");
45. System.out.println("j = " + j);
46. System.out.println("-- Load First SuperClass of static block End -- ");
47. }
48.
49. public A()...{
50. System.out.println("------- Load SuperClass of structor start --------");
51. System.out.println("Frist print j = " + j);
52. j = 10;
53. m();
54. System.out.println("k = " + k);
55. System.out.println("Second print j = " + j);
56. System.out.println("----------- Load SuperClass End ----------- ");
57. }
58.
59. private static int k = getInt();
60.
61. public static int getInt()...{
62. System.out.println("Load SuperClass.getInt() ");
63. return 11;
64. }
65. static...{
66. System.out.println("--- Load Second SuperClass of static block!-------");
67. System.out.println("j = " + j);
68. System.out.println("k = " + k);
69. System.out.println("-- Load Second SuperClass of static block End -- ");
70. }
71.
72. public void m()...{
73. System.out.println("SuperClass.m() , " + "j = " +j);
74.
75. }
76. }
77.
78. class B extends A ...{
79. private int a = 10;
80.
81. static...{
82. System.out.println("---- Load SubClass of static block!------");
83. System.out.println("-- Load SubClass of static block End -- ");
84. }
85.
86. public B()...{
87. System.out.println("Load SubClass of structor");
88. m();
89. System.out.println("--- Load SubClass End ---- ");
90. }
91.
92. public void m()...{
93. System.out.println("SubClass.m() ," + "a = " + a );
94. }
95. }
96.
97. public class Test1...{
98. public static void main(String[] args)...{
99. A a = new B();
100. }
101. }
102. 正确的答案为:
103. -- Load First SuperClass of static block start!--
104. j = 0
105. -- Load First SuperClass of static block End --
106. Load SuperClass.getInt()
107. --- Load Second SuperClass of static block!-------
108. j = 0
109. k = 11
110. -- Load Second SuperClass of static block End --
111. ---- Load SubClass of static block!------
112. -- Load SubClass of static block End --
113. ------- Load SuperClass of structor start --------
114. Frist print j = 0
115. SubClass.m() ,a = 0
116. k = 11
117. Second print j = 10
118. ----------- Load SuperClass End -----------
119. Load SubClass of structor
120. SubClass.m() ,a = 10
121. --- Load SubClass End ----
下面需要说明的一点也是至关重要的一点:那就是成员变量的初始化和非static初始化块之间的执行顺序是按照他们出现的先后顺序来执行的
[java] view plaincopy
1. public class Test04
2. {
3. //下面的这两行代码放置的顺序,跟执行结果是有关系的
4. private String t1 = test();
5.
6. {
7. System.out.println("初始化快!");
8. }
9. //上面的这两行代码放置的顺序,跟执行结果是有关系的
10.
11. private String test(){
12. System.out.println("实例变量的执行过程");
13. return "test";
14. }
15.
16. public Test04()
17. {
18. System.out.println("构造方法!");
19. }
20.
21. public static void main(String[] args)
22. {
23. Test04 t2 = new Test04();
24. }
25. }
最后在加上自己遇到的一段代码:
public class test {
public static void main(String[] args) {
Base b = new Sub();
System.out.println(b.x);
}
}
class Base {
int x = 10;
public Base() {
this.printMessage();
x = 20;
}
public void printMessage() {
System.out.println("Base.x = " + x);
}
}
class Sub extends Base {
int x =30 ;
public Sub() {
this.printMessage();
x = 40;
}
public void printMessage() {
System.out.println("Sub.x = " + x);
}
}
输出为:Sub.x = 0
Sub.x = 30
20
(Sub.x = 30,x = 30到底是什么时候初始化的呢,请看一下文章:)
-------------------------------------------------------------------------------------------------------------------
1 静态块优先
程序首先会执行静态块的内容,这也就有了不写main方法就跑hello world的小故事,相信说到这里,大家就有了思路。我们都知道静态类型是和类绑定的而不是和具体实例对象绑定。也就是说,引用一个静态变量的方式往往是MyClass.xxx.这个特点决定了其在编译的阶段就已经分配好了固定的空间。
2 父类优先
由于继承的特性,当导出类(子类)的对象被创建的时候,程序将向上追溯到最初的父类,执行其初始化的操作。然后一次向下调用子类的构造函数。按照这个思路,那么每一个实例第一个要初始化的必定是Object类了。
3 成员变量优先
一定要注意,成员变量按照其声明的顺序会被初始化,并且立刻被初始化为二进制的0,这个动作发生在所有事件之前,也就是编译器会立刻将分配给对象的空间初始化。一会的小例子将证明这一点。
最后就是调用类的构造方法了。
下面有一个不错的例子,为了演示成员变量最早被初始化为0了,我们将在父类的构造函数中调用子类的方法(利用了多态)。
1 package fruit;
2
3 import vege.Inner;
4
5
6 /**
7 * @author Octobershiner
8 */
9 public class Fruit {
10 //static block
11 static {
12 System.out.println("In Fruit static");
13 }
14
15 private Inner i = new Inner(); //a private member
16 public Fruit(){
17 System.out.println("Before Fruit Constructor");
18 show(); //由于多态的特性,此处子类Apple覆写的方法会被调用
19 System.out.println("After Fruit Constructor");
20 }
21 public void show(){
22 System.out.println("show:Fruit.");
23 }
24
25 public static void main(String[] args) {
26 // TODO code application logic here
27 new Apple(3);
28 }
29
30 }
现在父类中须要初始化的有
· 静态块
· 一个Inner类私有成员
· 构造函数
现在我们看子类的代码
1 package fruit;
2
3 public class Apple extends Fruit{
4 //静态块
5 static{
6 System.out.println("In Apple static");
7 }
8 private int weight = 1; //初始化为1 注意区别这里和 初始化为0
9
10 public Apple(int para_weight){
11 System.out.println("Before Apple Constructer: weight = "+weight);
12 weight = para_weight;
13 System.out.println("Apple Constructor: weight="+weight);
14 }
15
16 @Override
17 public void show(){
18 System.out.println("show apple: weight =" + weight);
19 }
20
21 }
子类须要初始化的有
· 静态块
· 私有成员weight
· 构造函数
那么当我们运行的时候会有怎样的结果呢?猜想。。。。。
下面就是执行的结果:
Look! 首先执行父类的静态块,之后是子类的静态块,这两个应该没有什么问题。接下来就是对父类成员变量的初始化了。首先是父类的私有成员Inner对象,打印了一条“ Inner Constructor”。
接下来就是父类的构造函数,可见由于java的多态性,Fruit的构造方法调用了其子类Apple的show方法,并且我们可以清晰的看到,此刻Apple类中weight变量的值是0!说明,类的成员变量无论是否赋值,在各种初始化之前早已被设置为二进制0了。
于是乎我想起了很多关于java的书都在说。。“如果类的私有变量没有赋值,就会被设置为0”。。这句话显然把时间弄混了。。。应该是编译器早已初始化了私有变量,均为0,之后才会执行到赋值语句。
父类的构造函数结束之后,再次回到子类,初始化私有变量(也就是我们常说的赋值语句,因为初始为0的工作早做完了)。所以我们才会看到“Before Apple Constructor weight = 1”,执行完构造函数后,我们就看到了weight终于变成了我们创建对象是传进的3了,呼,初始化结束。
总结
那么总结一下就是这样的:
1. 编译器初始化所有的已分配的空间为二进制0 (这是我们的私有变量都会为0,刚才的例子)
2. 执行父类静态代码 执行子类静态代码
3. 初始化父类成员变量(我们常说的赋值语句)
4. 初始化父类构造函数
5. 初始化子类成员变量
6. 初始化子类构造函数