初始化块
Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序,从而让该Java对象的信息更加完整,与构造器作用类似的是初始化块,它也可以对Java对象进行初始化。
初始化块是Java类里克出现的第四种成员(成员变量,方法和构造器),一个类里有多个初始化块,相同类型的初始化块之间有顺序:先定义的初始化块先执行,后定义的后执行
[修饰符]{
//初始化块的可执行性代码
...
}
初始化块的修饰符如果有则只能是static,这样的初始化块称为静态初始化块,修饰符可以没有
可执行性代码可以包含定义局部变量、调用其他对象的方法,以及使用分支、循环语句等任何可执行性语句
public class Person
{
// 下面定义一个实例初始化块
{
var a = 6;
if (a > 4)
{
System.out.println("Person实例初始化块:局部变量a的值大于4");
}
System.out.println("Person的实例初始化块");
}
// 定义第二个实例初始化块
{
System.out.println("Person的第二个实例初始化块");
}
// 定义无参数的构造器
public Person()
{
System.out.println("Person类的无参数构造器");
}
public static void main(String[] args)
{
new Person();
}
}
执行结果如下:
Person初始化块: 局部变量a的值大于4
Person的初始化块
Person的第二个初始化块
Person类的无参数构造器
- 系统总是先调用该类里定义的初始化块,如果一个类里定义了两个普通初始化块,则先定义的先执行后定义的后执行
- 初始化块虽然是Java类的一种成员,但是他没有名字,没有标识,所以无法通过类、对象来调用 ,只在创建Java对象时隐式的执行,并且在执行构造器之前执行
- 定义多个初始化块实际上没有任何意义,因为他们总是隐式的执行而且全部执行,完全可以把多个初始化块合并成为一个
普通的初始化块、声明实例变量指定的默认值都可以认为是对象的初始化代码,他们的执行顺序与源程序中的排列顺序相同
public class InstanceInitTest
{
// 先执行实例初始化块将a实例变量赋值为6
{
a = 6;
}
// 再执行将a实例变量赋值为9
int a = 9;
public static void main(String[] args)
{
// 下面代码将输出9。
System.out.println(new InstanceInitTest().a);
}
}
- 直接执行代码将输出9,这表明int a = 9这行代码在初始化块代码后执行,但如果将int a = 9放到初始化块代码前,程序将输出6,初始化块代码再次将a设为6
- Java创建一个对象的时候,系统先为该对象的所有实例变量分配内存(前提是该类已经被加载过了),接着程序开始对这些实例变量执行初始化,顺序是先执行初始化块或声明实例变量时指定的初始值(取决于源码中的顺序),在执行构造器里指定的初始值
构造器和初始化块的作用及区别
初始化块是构造器的补充,它总是在构造器之前执行,系统同样可以使用初始化块来进行对象的初始化操作
与构造器不同的是,初始化块是一段固定执行的代码,不接受参数,不能调用,因此初始化块对同一个类的所有对象所进行的初始化处理完全相同,如果有一段初始化代码对所有对象完全相同,且无需接受任何参数,便可以将这段代码提取到初始化块中
- 实际上初始化块是个假象,使用javac编译Java类后,该Java类中的初始化块会消失,被还原到每个构造器中,且位于构造器所有代码的前面。
- 与构造器类似,创建一个Java对象时,不仅会执行该类的普通初始化块和构造器,而且系统会一直追溯到java.lang.Object类,先执行java.lang.Object类的初始化块,然后执行java.lang.Object的构造器,依次向下执行其父类的初始化块,然后执行其父类的构造器…最后才执行该类的初始化块和构造器并返回该类的对象。
- 如果希望类加载后,对整个类进行某些初始化操作,则需要使用static关键字来修饰,即使用静态初始化块来初始化类
静态初始化块
- 初始化块代码如果使用了修饰符static,则被称为静态初始化块,或者类初始化块(普通初始化块负责对对象执行初始化,类初始化块负责对类进行初始化)
- 静态初始化块是类相关的,用于对整个类进行初始化处理,通常用于对类变量执行初始化处理,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行,因此静态初始化块总是比普通初始化块先执行
- 静态初始化块不能对实例变量进行初始化处理
- 静态初始化块也被称为类初始化块,也属于类的静态成员,同样需要遵循静态成员不能访问非静态成员的规则,包括不能访问实例变量和实例方法
- 与普通初始化块类似的是,系统在类初始化阶段执行静态初始化块时,不仅会执行本类的静态初始化块,而且会一直追溯到java.lang.Object类(如果它存在静态初始化块),先执行java.lang.Object类的静态初始化块,然后执行其父类的静态初始化块…最后才能执行该类的静态初始化块经过这个过程才能完成该类的初始化过程,只有当类初始化完成后才能使用它
class Root
{
static{
System.out.println("Root的类初始化块");
}
{
System.out.println("Root的实例初始化块");
}
public Root()
{
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root
{
static{
System.out.println("Mid的类初始化块");
}
{
System.out.println("Mid的实例初始化块");
}
public Mid()
{
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg)
{
// 通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid
{
static{
System.out.println("Leaf的类初始化块");
}
{
System.out.println("Leaf的实例初始化块");
}
public Leaf()
{
// 通过super调用父类中有一个字符串参数的构造器
super("疯狂Java讲义");
System.out.println("执行Leaf的构造器");
}
}
public class Test
{
public static void main(String[] args)
{
new Leaf();
new Leaf();
}
}
- 第一次创建一个Leaf对象时,因为系统还不存在Leaf类,因此需要先加载并初始化Leaf类,初始化Leaf类的时候会先执行其顶层父类的静态初始化块,在执行其直接父类的静态初始化块,最后才执行Leaf本身的静态初始化块
- Leaf类初始化成功后,它将在虚拟机中一直存在,因此当第二次创建Leaf实例时无须再次对Leaf类进行初始化
- 普通初始化块和构造器的执行顺序与前面介绍的一致,每次创建一个Leaf对象时,都需要先执行最顶层父类的初始化块、构造器然后执行其父类的普通初始化块、构造器…最后才执行Leaf类的初始化块和构造器
静态初始化块和声明静态成员变量时所指定的初始值都是该类的初始化代码,他们的执行顺序取决于源程序中的先后顺序
public class StaticInitTest
{
// 先执行类初始化块将类变量a赋值为6
static
{
a = 6;
}
// 再将类变量a赋值为9
static int a = 9;
public static void main(String[] args)
{
// 下面代码将输出9
System.out.println(StaticInitTest.a);
}
}
- 直接执行代码将输出9,这表明static int a = 9这行代码在静态初始化块代码后执行,但如果将static int a = 9放到静态初始化块代码前,程序将输出6,静态初始化块代码再次将a设为6
- 当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态成员变量分配内存,在初始化阶段则负责初始化这些静态成员变量,初始化静态成员变量就是执行类初始化块代码或者声明类成员变量时指定的初始值,顺序取决于他们在源程序的先后