Java初始化块
为了处理你需要的东西。值通常存储在静态变量、局部变量(在方法中定义)或实例变量(在类中定义的非静态变量)中。为了初始化一个变量,你可以在定义时或以后的方法(构造函数或不)。尽管有这两种常见的解决方案,还有另一种使用初始化块的方法。
初始化块(Initialization blocks)
是在{和}之间定义的代码块。
在这一点上,它们类似于方法块,但主要区别在于初始化块没有名称。它们类似于方法,但没有方法头(返回类型、名称、参数列表)。
像Java中的大多数东西一样,初始化块也是在类中定义的,而不是全局定义的。
初始化块的示例如下:
public class Main { //static initialization block static { System.out.println("这是一个静态初始化块"); } //initialization block { System.out.println("这是一个初始化块"); } public static void main(String[] args) { // TODO code application logic here System.out.println("这是main方法"); } }
如果运行上一个示例,则输出为:
这是一个静态初始化块 这是main方法
如我们所见,从前面的示例中,有两种类型的初始化块具有不同的行为:
静态初始化块——Java虚拟机加载类时只执行一次的代码块,这就是为什么在main()方法1之前打印静态初始化块消息的原因;这些类型的初始化块可用于初始化静态属性或仅执行一次一组语句;
初始化块(实例初始化块)–每次创建类实例时(调用构造函数时)执行的代码块;这些实例初始化块用于初始化静态属性或实现每次生成对象时执行的泛型处理,而不考虑调用的构造函数(可以有多个具有不同参数列表的构造函数)
根据这些描述,请看下面的例子
class Test{ //静态初始化块 static { System.out.println("这是静态初始化块!"); } // 初始化块 { System.out.println("这是初始化块"); } } public class Main { public static void main(String[] args) { System.out.println("main方法开始!"); Test t1 = new Test(); Test t2 = new Test(); System.out.println("main方法结束 !"); } }
输出结果为:
main方法开始! 这是静态初始化块! 这是初始化块! 这是初始化块! main方法结束!
正如我们在前面的示例中所看到的,正确地标识在哪个类中定义初始化块是很重要的,因为静态初始化块是在JVM加载类时执行的,并且每次创建实例时都会执行初始化块。
我们可以看到,在控制台输出中,第一条消息是main()方法的消息。解释是,JVM在创建第一个实例之前(通过*Test t1=new Test()*语句)加载测试类,但在主类已经加载之后。
与此相反,在第一个示例中,静态初始化块是在Main类中定义的,它是在JVM加载了Main类之后、Main()方法执行之前执行的。
在前面的例子中,如果我们注释行
//Test t1 = new Test(); //Test t2 = new Test();
那么输出是:
main方法开始! 这是静态初始化块! main方法结束!
我们可以定义任意多个初始化块(静态或非静态),但请注意它们的定义顺序,因为这是它们的执行顺序:
class Test{ //静态初始化块 static { System.out.println("这是静态初始化块1"); } // 初始化块 { System.out.println("这是初始化块1"); } // 初始化块 { System.out.println("这是初始化块2"); } //静态初始化块 static { System.out.println("这是静态初始化块2"); } } public class Main { public static void main(String[] args) { System.out.println("main方法开始!"); Test t1 = new Test(); Test t2 = new Test(); System.out.println("main方法结束 !"); } }
输出为:
main方法开始! 这是静态初始化块1 这是静态初始化块2 这是初始化块1 这是初始化块2 这是初始化块1 这是初始化块2 main方法结束!
因为:
加载Main类是为了执行Main();执行Main静态初始化bloc;执行Main()方法执行第一个测试静态初始化块是因为加载测试类是为了创建 t1instance;执行第二个测试静态初始化块是因为按顺序加载测试类要创建t1实例,创建第一个测试实例t1并执行第一个实例初始化块创建第一个测试实例t1并执行第二个实例初始化块…
初始化块和类变量
初始化块与类及其实例关联。所以让我们看看初始化块中可以使用哪些类型的类变量:
静态初始化块只能访问类静态变量;不能在静态初始化块中使用实例变量(如果尝试获取非静态变量值,则不能从静态上下文异常中引用);实例初始化块可以访问类静态变量和实例变量(实例变量是已构造实例的属性),因为它们是在创建实例之前执行的;
class Test { public int value; public static int staticValue; //static initialization block static { System.out.println("静态初始化块 - 1 !"); staticValue = 23; //value = 50; //compiler exception } //initialization block { System.out.println("初始化块 1!"); value = 50; } } public class Main { public static void main(String[] args) { Test t1 = new Test(); System.out.println("staticValue ="+Test.staticValue); System.out.println("t1.value = "+t1.value); } }
结果是
静态初始化块 - 1 ! 初始化块 1! staticValue =23 t1.value = 50
初始化块和类的层次结构
静态初始化块将在基类和子类中执行。
因为每个静态初始化块都是在加载其类之前执行的。
初始化块在构造函数调用super()后执行(基类构造函数);
class Base{ static { System.out.println("Base 类的静态初始化块 !"); } { System.out.println("Base 类的初始化块 !"); } public Base() { System.out.println("Base 类的构造函数 !");} } class Child extends Base{ public Child() { System.out.println("Child 类的构造函数 !"); } static { System.out.println("Child 类的静态初始化块 !"); } { System.out.println("Child 类的初始化块 !"); } } public class Main { public static void main(String[] args) { Child c = new Child(); } }
结果输出:
Base 类的静态初始化块 ! Child 类的静态初始化块 ! Base 类的初始化块 ! Base 类的构造函数 ! Child 类的初始化块 ! Child 类的构造函数 !
总结
静态初始化块只在JVM加载类时执行一次;
验证定义了main()的类和定义了初始化块的类,以确定输出;
每次创建类实例时执行初始化块(或实例初始化块);
实例初始化块按定义的顺序执行;
实例初始化块在构造函数调用super()之后执行;
实例初始化块可以访问实例变量和类静态变量;
静态初始化块只能访问类静态变量。