4.9 初始化块
一、使用初始化块
初始化块的语法格式:
1 [修饰符]{ 2 //初始化块的可执行代码 3 ... 4 }
初始化块的修饰符只能是static,用static修饰的初始化块被称为类初始化块(静态初始化块),没有static修饰的初始化块称为实例初始化块(非静态初始化块)。
一个类里可以定义多个初始化块,前面定义的初始化块先执行,后买你定义的初始化块后执行,而且都是在构造器之前执行。
1 class InitTest 2 { 3 public int a; 4 { 5 a=6; 6 System.out.println("第一个初始化块:a="+a); 7 } 8 public InitTest(int a) 9 { 10 this.a=a; 11 System.out.println("执行构造器中的初始化块:a="+a); 12 } 13 { 14 int a=9; 15 System.out.println("第二个初始化块:a="+a); 16 } 17 public static void main(String[] args) 18 { 19 var i=new InitTest(10); 20 } 21 } 22 ---------- 运行Java捕获输出窗 ---------- 23 第一个初始化块:a=6 24 第二个初始化块:a=9 25 执行构造器中的初始化块:a=10 26 27 输出完成 (耗时 0 秒) - 正常终止
Java程序创建一个对象时,系统先为该对象的所有实例变量分配内存(前提是该类以及被加载过),接着程序开始对这些事例变量执行初始化,其初始化的顺序是:先执行实例初始化块或声明实例变量时指定的初值(这与代码块的顺序有关,先出现的先执行),再执行构造器里的初始值。
二、实例初始化块和构造器
与构造器不同的是,实例初始化块是一块固定执行的代码,它不能接受任何参数。因此实例初始化块对同一个类的所有对象所进行的初始化块处理完全相同。如果有一段初始化对所有对象完全相同,且无须接受任何参数,就可以把这段初始化处理代码提取到实例初始化块中。如图所示:
注:实际上实例初始化块是一个假象,使用javac命令编译Java类后,该Java类中的实例初始化块就会消失——实例初始化块中的代码会被“还原”到每个构造器中,且位于构造器所有代码的前面。故初始化块总是先于构造器之前执行。
与构造器类似的,创建一个Java对象时,不仅会执行该类的初始化块和构造器,而且系统会上溯到java.lang.Object类,先执行java.lang.Object类的实例初始化块,再执行java.lang.Object类的构造器块,依次向下执行其父类的实例初始化块,再执行父类的构造器...最后才是该类实例的初始化块和构造器,返回该类的对象。
三、类初始化块
无static修饰的初始化块称为实例初始化块。实例初始化块负责对对象执行初始化,在创建对象时就执行。
static修饰的初始化块就变成了类初始化块,也称为静态初始化块。系统将在类初始化阶段执行类初始化块,而不是创建对象时执行。因此类初始化块总是先于实例初始化块先执行。
与类初始化块类似的是,系统在类初始化阶段执行类初始化块时,,不仅会执行本类的类初始化块,而且会一直上溯到java.lang.Object类(如果它包含类初始化块),先执行java.lang.Object类的类初始化块(如果有),然后执行其父类的初始化块......最后才执行该类的初始化块,经过该过程完成该类的初始化过程。只有当类初始化完成后,才可以在系统中使用这个类,包括类变量、类方法或则用这个类来创建实例。
1 class Root 2 { 3 static { 4 System.out.println("Root类初始化块"); 5 } 6 public Root() 7 { 8 System.out.println("执行Root的构造器"); 9 } 10 { 11 System.out.println("执行Root类的实例化变量"); 12 } 13 } 14 15 class Mid extends Root 16 { 17 static { 18 System.out.println("Mid类初始化块"); 19 } 20 { 21 System.out.println("Mid类的实例初始化块"); 22 } 23 public Mid() 24 { 25 System.out.println("Mid类的无参数构造器"); 26 } 27 public Mid(String msg) 28 { 29 //通过this调用同一个类中的重载构造器 30 this(); 31 System.out.println("Mid类带参数的构造器,其参数值为"+msg); 32 } 33 } 34 class Leaf extends Mid 35 { 36 static{ 37 System.out.println("Leaf类的初始化块"); 38 } 39 { 40 System.out.println("Leaf类的实例初始化块"); 41 } 42 public Leaf() 43 { 44 //通过super调用父类有一个字符串参数的构造器 45 super("疯狂Java讲义"); 46 System.out.println("执行Leaf的构造器"); 47 } 48 } 49 public class Test 50 { 51 public static void main(String[] args) 52 { 53 new Leaf(); 54 new Leaf(); 55 } 56 } 57 ---------- 运行Java捕获输出窗 ---------- 58 Root类初始化块 59 Mid类初始化块 60 Leaf类的初始化块 61 执行Root类的实例化变量 62 执行Root的构造器 63 Mid类的实例初始化块 64 Mid类的无参数构造器 65 Mid类带参数的构造器,其参数值为疯狂Java讲义 66 Leaf类的实例初始化块 67 执行Leaf的构造器 68 执行Root类的实例化变量 69 执行Root的构造器 70 Mid类的实例初始化块 71 Mid类的无参数构造器 72 Mid类带参数的构造器,其参数值为疯狂Java讲义 73 Leaf类的实例初始化块 74 执行Leaf的构造器 75 76 输出完成 (耗时 0 秒) - 正常终止
上面定义的三个类的,其继承树如图所图:
当第一次创建Leaf对象时。因为系统还不存在Leaf类,因此需要加载并初始化类,初始化类从上而下执行,如图中箭头1、2、3的顺序执行。
一旦类初始化块完成后,就可以创建对象了。在分析时,实例初始化块可以看作是构造器的执行块的第一部分,实例初始化块总是先于构造器之前执行,可以将实例初始化块和构造器看成“构造器”。在执行Leaf“构造器”之前,先执行Mid带一个参数的“构造器”,在执行Mid“构造器”之前,先执行Root类“构造器”。输出结果为:
1 执行Root类的实例化变量 2 执行Root的构造器 3 Mid类的实例初始化块 4 Mid类的无参数构造器 5 Mid类带参数的构造器,其参数值为疯狂Java讲义 6 Leaf类的实例初始化块 7 执行Leaf的构造器
同一个类中的多个类初始化块的执行顺序与实例初始化块的执行顺序一致。先出现的先执行。