什么是初始化块
在Java类中,除了属性和方法之外,还有一类特殊的代码块,被称为初始化块。初始化块没有名字,也没有参数,按照是否用static修饰,分为静态初始化块和实例初始化块两种。它们的作用是在类加载或者对象创建时,执行一些固定的初始化逻辑,很多时候可以替代构造器中重复的代码,让结构更清晰。

静态初始化块详解
定义与语法
静态初始化块是用static关键字修饰的初始化块,它的语法格式非常简单,就是在类内部直接写一个被static修饰的代码块:
public class StaticBlockDemo {
// 静态初始化块
static {
System.out.println("静态初始化块执行了");
}
}静态初始化块属于类级别的代码,不属于任何一个实例对象,它的执行时机和类加载过程绑定。
执行时机与次数
静态初始化块的执行发生在类被首次主动使用的时候,JVM加载类的过程中,在类初始化阶段会执行静态初始化块。根据Java类加载的机制,同一个类加载器下,一个类只会被加载一次,因此静态初始化块在整个程序运行期间,只会执行一次。
哪些情况属于类的主动使用呢?常见的有:创建类的实例、访问类的静态变量或者静态方法、反射调用类、初始化类的子类时父类还没初始化等。如果只是通过类名访问类的静态常量(final修饰的基本类型或字符串),且常量在编译期就能确定值,不会触发类加载,也就不会执行静态初始化块。
典型使用场景
- 初始化类的静态变量,尤其是需要复杂逻辑计算的静态变量,比如读取配置文件初始化全局配置项。
- 加载类所需的静态资源,比如加载数据库驱动、初始化日志组件等只需要做一次的操作。
- 执行类级别的校验逻辑,比如检查运行环境是否满足要求。
实例初始化块详解
定义与语法
实例初始化块是没有static修饰的普通初始化块,语法上就是一个直接写在类内部的代码块,不需要任何修饰符:
public class InstanceBlockDemo {
// 实例初始化块
{
System.out.println("实例初始化块执行了");
}
}实例初始化块属于对象级别的代码,每次创建类的实例时,都会被触发执行。
执行时机与次数
实例初始化块的执行发生在对象创建的过程中,在构造器执行之前。准确来说,编译器会把实例初始化块的代码,复制到每一个构造器的开头,不管你定义了多少个构造器,每个构造器都会包含实例初始化块的逻辑。因此每次通过new关键字创建对象的时候,实例初始化块都会执行一次,创建多少个对象就执行多少次。
典型使用场景
- 多个构造器有公共的初始化逻辑,把这些公共逻辑放到实例初始化块中,可以避免在每个构造器里重复写相同的代码。
- 初始化对象的实例变量,尤其是需要统一初始化规则的非静态属性。
- 执行对象创建时必须做的校验或者预处理逻辑,只要创建对象就会执行,不需要依赖具体调用的构造器。
两者的执行顺序对比
要彻底理解静态初始化块和实例初始化块,最核心的就是搞清楚它们的执行顺序,我们通过一个包含父类和子类的示例来验证:
// 父类
class Parent {
// 父类静态初始化块
static {
System.out.println("父类静态初始化块执行");
}
// 父类实例初始化块
{
System.out.println("父类实例初始化块执行");
}
// 父类构造器
public Parent() {
System.out.println("父类构造器执行");
}
}
// 子类
class Child extends Parent {
// 子类静态初始化块
static {
System.out.println("子类静态初始化块执行");
}
// 子类实例初始化块
{
System.out.println("子类实例初始化块执行");
}
// 子类构造器
public Child() {
System.out.println("子类构造器执行");
}
}
// 测试类
public class InitOrderTest {
public static void main(String[] args) {
System.out.println("第一次创建子类对象:");
Child c1 = new Child();
System.out.println("\
第二次创建子类对象:");
Child c2 = new Child();
}
}运行上面的代码,输出结果如下:
第一次创建子类对象: 父类静态初始化块执行 子类静态初始化块执行 父类实例初始化块执行 父类构造器执行 子类实例初始化块执行 子类构造器执行 第二次创建子类对象: 父类实例初始化块执行 父类构造器执行 子类实例初始化块执行 子类构造器执行
从结果可以总结出完整的执行顺序规则:
- 首次使用类时,先执行父类的静态初始化块,再执行子类的静态初始化块,且静态初始化块只执行一次。
- 每次创建对象时,先执行父类的实例初始化块,再执行父类的构造器。
- 然后执行当前类的实例初始化块,再执行当前类的构造器。
- 如果创建多个对象,静态初始化块不会再执行,只重复执行实例初始化块和构造器。

和构造器的区别与联系
很多初学者会把实例初始化块和构造器搞混,其实两者有明确的区别:
| 对比项 | 实例初始化块 | 构造器 |
|---|---|---|
| 修饰符 | 无 | 可以有访问修饰符(public、private等) |
| 参数 | 不能定义参数 | 可以定义参数 |
| 执行逻辑 | 所有构造器共享,固定逻辑 | 不同构造器可以有不同逻辑 |
| 调用方式 | 不能主动调用,随对象创建自动执行 | 可以通过new主动调用,也可以构造器之间互相调用 |
联系在于:编译器会把实例初始化块的代码,插入到每一个构造器的起始位置,所以实例初始化块的执行一定在构造器之前,相当于构造器执行的前置公共步骤。
使用注意事项
- 静态初始化块不能访问实例变量和实例方法,因为静态初始化块执行时可能还没有创建任何实例对象,实例成员还不存在。
- 实例初始化块可以访问静态变量和静态方法,也可以访问实例变量,但此时实例变量还没有被构造器初始化,只能访问默认值或者通过初始化块赋值后的值。
- 如果一个类有多个静态初始化块或者多个实例初始化块,它们会按照在类中定义的顺序依次执行。
- 不要在初始化块中写过于复杂的逻辑,否则会让代码可读性下降,尤其是多个初始化块嵌套的时候,排查问题会比较麻烦。
- 静态初始化块如果抛出异常,会导致类初始化失败,后续所有对该类的主动使用都会抛出
ExceptionInInitializerError,要提前做好异常处理。
总结
静态初始化块和实例初始化块是Java中用来做初始化逻辑的重要语法,核心区别体现在归属级别、执行时机和执行次数上:静态初始化块属于类,类加载时执行一次,适合做类级别的全局初始化;实例初始化块属于对象,每次创建对象都执行,适合放多个构造器的公共初始化逻辑。理清两者的执行顺序,尤其是结合继承场景下的执行规则,能帮你避免很多初始化相关的问题,写出更规范的Java代码。