对于JAVA中类的初始化是一个很基础的问题,其中的一些问题也是易被学习者所忽略。当在编写代码的时候碰到时,常被这些问题引发的错误,感觉莫名其妙。而且现在许多大公司的面试题,对于这方面的考查也是屡试不爽。不管基于什么原因,我认为,对于java类中的初始化问题,有必要深入的了解。Java类的初始化,其实就是它在JVM的初始化问题(类加载的问题),对于它在JVM中的初始化是一个相当复杂的问题,是给专家们来探讨的,所以在这里我只是对一些容易忽略的问题,发表一下个人观点:
1,在一个类的内部(不考虑它是另一个类的派生类):很多人认为,类的成员变量是在构造方法调用之后再初始化的,先不考虑这种观点的正确性,先看一下下面的代码:
[java] view plaincopy
-
class Test01...{
-
public Test01(int i)...{
-
System.out.println("Test01 of constractor : " + i);
-
}
-
}
-
public class Test02 ...{
-
private Test01 t1 = new Test01(1);
-
private int n = 10;
-
-
public Test02()...{
-
System.out.println("Test02 of constructor : " + n);
-
}
-
private Test01 t2 = new Test01(2);
-
public static void main(String[] args) ...{
-
Test02 test = new Test02();
-
}
-
-
}
-
输出的结果为:
-
Test01 of constractor : 1
-
Test01 of constractor : 2
-
Test02 of constructor : 10
通过输出,可见当生成Test02的实例test时,它并不是首先调用其构造方法而是先是成员变量的初始化,而且成员的初始化的顺序以成员变量的定义顺序有关,先定义的先初始化,初始化后再调用构造方法。其实成员变量的初始化,在类的所有方法调用之前进行,包括构造方法
当类中有Static 修饰的成员呢?测试下面一段代码:
[java] view plaincopy
-
public class Test03 ...{
-
private int i1 = printCommon();
-
private static int i2 = printStatic();
-
-
public Test03()...{
-
-
}
-
public static int printCommon()...{
-
System.out.println("i1 is init!");
-
return 1;
-
}
-
public static int printStatic()...{
-
System.out.println("i2 is init!");
-
return 2;
-
}
-
public static void main(String[] args) ...{
-
Test03 t = new Test03();
-
}
-
}
-
-
输出结果为:
-
i2 is init!
-
i1 is init!
可见static的成员比普通的成员变量先初始化。
我们都知道,如果一个类的成员变量没有在定义时,系统会给予系统默认的值,有=号的就直接给予右值,系统在给予初值和=号给予值这2中方式,在执行时间上有先后吗?为了测试,我编写了如下代码:
[java] view plaincopy
-
public class Test04 ...{
-
private static Test04 t1 = new Test04();
-
private static int i1;
-
private static int i2 = 2;
-
-
public Test04()...{
-
i1++;
-
i2++;
-
}
-
-
public static void main(String[] args) ...{
-
Test04 t2 = new Test04();
-
System.out.println("t2.i1 = " + t2.i1);
-
System.out.println("t2.i2 = " + t2.i2);
-
}
-
}
-
我们先预计一下输出,可能有几种答案:2和3,3和3,2和2
-
执行代码后:
-
t2.i1 = 2
-
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
-
class SuperClass ...{
-
static...{
-
System.out.println("SuperClass of static block");
-
}
-
-
public SuperClass()...{
-
System.out.println("SuperClass of constracutor");
-
}
-
}
-
-
public class SubClass extends SuperClass...{
-
static...{
-
System.out.println("SubClass of static block");
-
}
-
-
public SubClass()...{
-
System.out.println("SubClass of constracutor");
-
}
-
-
public static void main(String[] args)...{
-
SuperClass t = new SubClass();
-
}
-
}
-
输出结果:
-
SuperClass of static block
-
SubClass of static block
-
SuperClass of constracutor
-
SubClass of constracutor
可见当父类,和子类有Static时,先初始化Static,再初始化子类的Static,再初始化父类的其他成员变量->父类构造方法->子类其他成员变量->子类的构造方法。
父类上层还有父类时,总是先执行最顶层父类的Static-->派生类Static-->派生类Static-->.......-->子类Static-->顶层父类的其他成员变量-->父类构造方法--> 派生类的其他成员变量 --> 派生类构造方法--> ...............-->子类其他成员变量-->子类构造方法
讨论到继承,就不得提一下多态:
如果父类构造方法的代码中有子类中被重写得方法,当执行这样的语句
SuperClass super = new SubClass();
初始化时调用父类的构造方法,是执行父类的原方法,还是执行子类中被重写的方法呢?
[java] view plaincopy
-
class SuperClass...{
-
public SuperClass()...{
-
System.out.println("SuperClass of constructor");
-
m();
-
}
-
public void m()...{
-
System.out.println("SuperClass.m()");
-
}
-
}
-
public class SubClassTest extends SuperClass ...{
-
private int i = 10;
-
public SubClassTest()...{
-
System.out.println("SubClass of constructor");
-
super.m();
-
m();
-
}
-
public void m()...{
-
System.out.println("SubClass.m(): i = " + i);
-
}
-
public static void main(String[] args)...{
-
SuperClass t = new SubClassTest();
-
}
-
}
-
可能很多人会认为输出为:
-
SuperClass of constructor
-
SubClass.m(): i = 10
-
SubClass of constructor
-
SuperClass.m()
-
SubClass.m(): i = 10
-
其实不然!
-
正确输出为:
-
SuperClass of constructor
-
SubClass.m(): i = 0
-
SubClass of constructor
-
SuperClass.m()
-
SubClass.m(): i = 10
-
在生成对象时,父类调用的M()方法,不是父类的 M()方法,而时子类中被重写了的M()方法!!并且还出现一个怪异的现象,子类的privte int i 也被父类访问到,这不是和我们说private的成员只能在本类使用的原则相违背了吗?其实我们说的这条原则是编译期间所遵守的,在JAVA程序的编译期间,它只检查语法的合法性,在JAVA的JVM中,即运行期间,不管你声明的什么,对于JVM来说都是透明的,而多态是在运行期间执行的,所以能拿到SubClass的private成员,一点都不奇怪,只是此时还没执行 i = 10,所以在父类的构造方法中调用m()时,系统只能将i赋予系统初值0。
-
下面是我设计的一道完整的初始化例子,可测试你对类的初始化问题是否完整掌握:
-
写出程序运行的结果:
-
class A...{
-
private int i = 9;
-
protected static int j;
-
static...{
-
System.out.println("-- Load First SuperClass of static block start!-- ");
-
System.out.println("j = " + j);
-
System.out.println("-- Load First SuperClass of static block End -- ");
-
}
-
-
public A()...{
-
System.out.println("------- Load SuperClass of structor start --------");
-
System.out.println("Frist print j = " + j);
-
j = 10;
-
m();
-
System.out.println("k = " + k);
-
System.out.println("Second print j = " + j);
-
System.out.println("----------- Load SuperClass End ----------- ");
-
}
-
-
private static int k = getInt();
-
-
public static int getInt()...{
-
System.out.println("Load SuperClass.getInt() ");
-
return 11;
-
}
-
static...{
-
System.out.println("--- Load Second SuperClass of static block!-------");
-
System.out.println("j = " + j);
-
System.out.println("k = " + k);
-
System.out.println("-- Load Second SuperClass of static block End -- ");
-
}
-
-
public void m()...{
-
System.out.println("SuperClass.m() , " + "j = " +j);
-
-
}
-
}
-
-
class B extends A ...{
-
private int a = 10;
-
-
static...{
-
System.out.println("---- Load SubClass of static block!------");
-
System.out.println("-- Load SubClass of static block End -- ");
-
}
-
-
public B()...{
-
System.out.println("Load SubClass of structor");
-
m();
-
System.out.println("--- Load SubClass End ---- ");
-
}
-
-
public void m()...{
-
System.out.println("SubClass.m() ," + "a = " + a );
-
}
-
}
-
-
public class Test1...{
-
public static void main(String[] args)...{
-
A a = new B();
-
}
-
}
-
正确的答案为:
-
-- Load First SuperClass of static block start!--
-
j = 0
-
-- Load First SuperClass of static block End --
-
Load SuperClass.getInt()
-
--- Load Second SuperClass of static block!-------
-
j = 0
-
k = 11
-
-- Load Second SuperClass of static block End --
-
---- Load SubClass of static block!------
-
-- Load SubClass of static block End --
-
------- Load SuperClass of structor start --------
-
Frist print j = 0
-
SubClass.m() ,a = 0
-
k = 11
-
Second print j = 10
-
----------- Load SuperClass End -----------
-
Load SubClass of structor
-
SubClass.m() ,a = 10
-
--- Load SubClass End ----
下面需要说明的一点也是至关重要的一点:那就是成员变量的初始化和非static初始化块之间的执行顺序是按照他们出现的先后顺序来执行的
[java] view plaincopy
-
public class Test04
-
{
-
//下面的这两行代码放置的顺序,跟执行结果是有关系的
-
private String t1 = test();
-
-
{
-
System.out.println("初始化快!");
-
}
-
//上面的这两行代码放置的顺序,跟执行结果是有关系的
-
-
private String test(){
-
System.out.println("实例变量的执行过程");
-
return "test";
-
}
-
-
public Test04()
-
{
-
System.out.println("构造方法!");
-
}
-
-
public static void main(String[] args)
-
{
-
Test04 t2 = new Test04();
-
}
-
-
}
但是要注意: 如果初始化块在变量定义之前,初始化块调用变量会报错。
总结初始化块
1.初始化块在构造方法调用前执行
2.初始化块调用的时机与位置有关
如果放在类的开头,对变量初始化会出错,因为变量还没有定义,
因此 初始化块放在类的末尾,变量的声明定义放在类首。
来源:oschina
链接:https://my.oschina.net/u/1247524/blog/304354