您的位置:首页 > 编程语言 > Java开发

Java类的初始化顺序 (静态变量、静态初始化块、变量、初始...

2014-04-09 10:31 513 查看
初始化块与构造方法的作用很相似,他们都用于对Java对象执行指定的初始化块操作,但两者之间仍然存在着差异。具体差异在于:初始化块是一段固定的执行代码,他不能接收任何参数,因此初始化块对同一个类内的属性所进行的初始化处理完全相同;如果多个构造方法里有相同的初始化块,这些初始化代码无需接受参数,那就可以把他们放在初始化中定义。通过把多个构造方法中相同的代码提取到初始化块中定义,能更好的提高初始化块的复用,提高应用的可维护性

与构造方法类似,创建一个Java对象时,不仅会执行该类的初始化块和构造方法,系统还会一直追溯到java.lang.Object类,先执行java.lang.Object类的初始化块,执行java.lang.Object的构造方法,然后依次向下执行父类的初始化块,执行其父类的构造方法….最后才执行给类的初始化块和构造方法,返回该类的对象

如果希望类加载后对整个类执行某些初始化操作,则需要使用static关键字来修饰初始化块,使用static修饰的初始化块呗称为静态初始化块,静态初始化块是类相关的,静态初始化块必普通初始化块先执行

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。这实际上是在考查我们对于继承情况下类的初始化顺序的了解。

我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序以此是(静态变量、静态初始化块)>(变量、初始化块)>构造器。我们也可以通过下面的测试代码来验证这一点:

Java代码

public class InitialOrderTest {

// 静态变量

public static String staticField = "静态变量";

// 变量

public String field = "变量";

// 静态初始化块

static {

System.out.println(staticField);

System.out.println("静态初始化块");

}

// 初始化块

{

System.out.println(field);

System.out.println("初始化块");

}

// 构造器

public InitialOrderTest() {

System.out.println("构造器");

}

public static void main(String[] args) {

new InitialOrderTest();

}

}

运行以上代码,我们会得到如下的输出结果:

静态变量

静态初始化块

变量

初始化块

构造器

这与上文中说的完全符合。那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:

Java代码

class Parent {

// 静态变量

public static String p_StaticField = "父类--静态变量";

// 变量

public String p_Field = "父类--变量";

// 静态初始化块

static {

System.out.println(p_StaticField);

System.out.println("父类--静态初始化块");

}

// 初始化块

{

System.out.println(p_Field);

System.out.println("父类--初始化块");

}

// 构造器

public Parent() {

System.out.println("父类--构造器");

}

}

public class SubClass extends Parent {

// 静态变量

public static String s_StaticField = "子类--静态变量";

// 变量

public String s_Field = "子类--变量";

// 静态初始化块

static {

System.out.println(s_StaticField);

System.out.println("子类--静态初始化块");

}

// 初始化块

{

System.out.println(s_Field);

System.out.println("子类--初始化块");

}

// 构造器

public SubClass() {

System.out.println("子类--构造器");

}

// 程序入口

public static void main(String[] args) {

new SubClass();

}

}

运行一下上面的代码,结果马上呈现在我们的眼前:

父类--静态变量

父类--静态初始化块

子类--静态变量

子类--静态初始化块

父类--变量

父类--初始化块

父类--构造器

子类--变量

子类--初始化块

子类--构造器

现在,结果已经不言自明了。大家可能会注意到一点,那就是,并不是父类完全初始化完毕后才进行子类的初始化,实际上子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。

那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。我们以静态变量和静态初始化块为例来进行说明。

同样,我们还是写一个类来进行测试:

Java代码

public class TestOrder {

// 静态变量

public static TestA a = new TestA();

// 静态初始化块

static {

System.out.println("静态初始化块");

}

// 静态变量

public static TestB b = new TestB();

public static void main(String[] args) {

new TestOrder();

}

}

class TestA {

public TestA() {

System.out.println("Test--A");

}

}

class TestB {

public TestB() {

System.out.println("Test--B");

}

}

运行上面的代码,会得到如下的结果:

Test--A

静态初始化块

Test--B

大家可以随意改变变量a、变量b以及静态初始化块的前后位置,就会发现输出结果随着它们在类中出现的前后顺序而改变,这就说明静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。同样,变量和初始化块也遵循这个规律。

了解了继承情况下类的初始化顺序之后,如何判断最终输出结果就迎刃而解了。

静态块与静态成员的初始化工作与实例化过程无关,实例化必须先执行静态块和静态成员,但并不代表实例化一定会执行静态块和静态成员。只有当实例化的对应的类为加载入虚拟机的时候,才会进行这种操作。有些时候执行静态块或者初始化静态成员不一定就是实例化该类对象才会进行的,例如调研该类的某静态成员或者静态方法,又例如该类的子类被实例化或者调用了静态成员或静态方法等。

还有实例化的实际顺序其实是(省略类初始化过程)

1、进入当前类构造方法。

2、进入父类构造方法递归直到java.lang.Object类构造方法。

3、执行java.lang.Object类构造方法,顺序依次为成员变量初始与初始化块(安装上下文顺序),对应调用的构造方法体。

4、执行java.lang.Object类的直接子类的构造函数,这个过程递归到当前类。

5、当前类执行顺序与前面java.lang.Object类相同。

构造方法的本质其实就是一个普通的无返回参数的名字叫做<init>的方法,不过虚拟机调用这个方法的指令与其它方法不同而已,它的调用指令与调用private方法的指令相同。

在虚拟机中存在三种方法的调用指令,这三种调用指令在效率上不同。

接口方法的指令调用,这种调用速度最慢。

普通类方法的调用指令,这种调用速度中等。

构造方法与私有方法调用指令,这种调用速度最快。

=====================================================================================================

初始化块是什么?在Java中就是在构造器之前执行的一段代码,它的用处是,如果一段初始化处理代码对所有对象完全相同,且无须接受任何参数,就可以把这段提取到初始化块中.在C#中,没有初始化块

[java] view
plaincopyprint?

public class Person

{

//定义一个初始化块

{

int a = 6;

//在初始化块中

if (a >4)

{

System.out.println("Person 初始化块:局部变量a的值大于4");

}

}

//定义第二个初始化块

{

System.out.println("Person的第二个初始化块");

}

//定义无参数的构造器

public Person()

{

System.out.println("Person的无参数构造器");

}

public static void main(String[] args)

{

new Person();

}

}

输出

Person 初始化块:局部变量a的值大于4

Person的第二个初始化块

Person的无参数构造器

初始化块的执行顺序是按定义的顺序执行,最后才轮到构造器

初始化块又分为非静态初使化块和静态初始化块,静态初始化块必须要用static进行修饰,静态初始化块是对类而言,非静态初始化块是对对象而言,但静态初始化块的执行顺序在前,非静态在后.

[java] view
plaincopyprint?

public class Root

{

static{

System.out.println("Root 的静态初始化块");

}

{

System.out.println("Root 的普通初始化块");

}

public Root()

{

System.out.println("Root 的无参数的构造器");

}

}

public class Mid extends Root

{

static{

System.out.println("Mid 的静态初始化块");

}

public Mid()

{

System.out.println("Mid 的无参数的构造器");

}

public Mid(String msg)

{

//通过this调用同一类中重载的构造器

this();

System.out.println("Mid的带参构造器,其参数值:" + msg);

}

}

public class Leaf extends Mid

{

static{

System.out.println("Leaf 的静态初始化块");

}

{

System.out.println("Leaf 的普通初始化块");

}

public Leaf()

{

//通过surper 调用父类中有一个字符串参数的构造器

super("Struts 2权威指南");

System.out.println("执行Leaf的构造器");

}

}

public class Test

{

public static void main(String[] args)

{

new Leaf();

new Leaf();

}

}



从上面的执行结果可以看出,类初始化阶段,先执行最顶层父类的静态初始化块,依次向下,最后执行当前类静态初始化块,对象初始化阶段,先执行最顶层父类的初始化块,构造器,依次向下,最后执行当前类初始化块,构造器

分析上面的例子,主程序两次执行new Leaf(); ,创建2个Leaf对象,第一次创建Leaf对象时,系统中还不存在Leaf类,因此需要先加载并初始化Leaf类,初始化Leaf类时会先执行其顶层父类的静态初始化块,最后才执行Leaf本身的静态初始化块.

一旦Leaf类初始化成功后,Leaf类在该虚拟机里将一直存在,因此当第二次创建Leaf实例时无须再次对Leaf类进行初始化.

所以这个执行的优先级为 : 所有类的静态初始化块(从父类自顶向下) -> 所有类的普通初始化块然后接着类的构选器(从父类自顶向下)

如果再实例化一个对象,那么就没有类初始化这个步骤了,因为已经发生在第一次实例化了,类初始化只会发生一次!且在该类的所有对象实例化之前
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐