在Java语言中,代码块是指用一对大括号包裹、没有名称和返回值的语句段,直接写在类体或方法体中。按照是否使用static修饰,最常见的两类是静态代码块和构造代码块。它们虽写法相似,却在生命周期与执行时机上完全不同,这也是很多人在写工具类或单例对象时容易踩坑的地方。

一、Java代码块的基础概念
静态代码块使用static关键字修饰,格式为static { ... },它属于类级别的结构。当JVM第一次加载该类时,会执行静态代码块,且整个程序运行期间只执行这一次。通常我们会在这里做驱动注册、全局配置读取等只需一次的动作。
构造代码块则没有static,直接写{ ... },它属于对象级别。每当使用new创建该类实例时,构造代码块都会在构造方法主体之前被执行。也就是说,无论调用哪一个构造器,构造代码块中的逻辑都会先跑一遍,适合放多个构造器共享的初始化代码。
1.1 基础写法示例
下面这段代码展示了两种代码块的最简形式,以及它们在类里的位置:
public class Demo {
// 静态代码块
static {
System.out.println("静态代码块执行");
}
// 构造代码块
{
System.out.println("构造代码块执行");
}
// 构造方法
public Demo() {
System.out.println("构造方法执行");
}
}
从结构可以看到,静态块和构造块都独立于方法存在。它们不是方法,不能被显式调用,完全由虚拟机在类加载和对象实例化阶段自动触发。
二、静态代码块与构造代码块的执行顺序
要理清顺序,必须站在JVM视角看:类加载优先于对象创建。因此静态代码块一定早于任何构造代码块。而在同一个类的实例化过程中,构造代码块又早于构造方法。如果涉及继承,规则会再外扩一层。
具体次序为:父类静态块 → 子类静态块 → 父类构造块 → 父类构造器 → 子类构造块 → 子类构造器。这个链路保证了父类资源先就位,子类才能安全使用。很多框架的上下文初始化就是利用这一特性完成的。
2.1 单类场景下的顺序验证
我们用一段可运行程序观察无继承时的输出:
public class OrderTest {
static {
System.out.println("1. 静态代码块");
}
{
System.out.println("2. 构造代码块");
}
public OrderTest() {
System.out.println("3. 构造方法");
}
public static void main(String[] args) {
new OrderTest();
new OrderTest();
}
}
运行后控制台会打印:第一次new时输出1、2、3;第二次new时只输出2、3。这证明静态块仅随类加载走一次,而构造块每次建对象都重跑。如果误以为静态块每次都执行,就会把计数或连接池初始化写错位置。
2.2 继承场景下的顺序验证
当存在父类时,顺序差异更明显,看下面例子:
class Parent {
static { System.out.println("P 静态块"); }
{ System.out.println("P 构造块"); }
Parent() { System.out.println("P 构造器"); }
}
class Child extends Parent {
static { System.out.println("C 静态块"); }
{ System.out.println("C 构造块"); }
Child() { System.out.println("C 构造器"); }
}
public class ExtTest {
public static void main(String[] args) {
new Child();
}
}
输出依次是:P静态块、C静态块、P构造块、P构造器、C构造块、C构造器。可见静态部分先自上而下走完,再进入对象实例化链路。理解这一点,在写基类通用资源释放或子类专属配置时,就不会出现空指针或覆盖错误。
三、常见误区与正确使用建议
一个典型误区是把数据库驱动注册放进构造代码块,导致每次new对象都注册一次,轻则警告重则连接异常。正确做法是放在静态块,确保只注册一回。另一个误区是在构造块里依赖静态块中尚未赋值的静态变量,虽然同类加载时静态块先跑,但若是复杂类间引用仍可能踩到时序盲区。
建议把只需一次的、与实例无关的逻辑全放静态块;把每个对象都需要的字段预设或校验放构造块;构造方法只写特定于该重载的参数处理。配合日志打印或IDE断点,能快速确认执行流。这样分层后,代码可维护性会明显提升,也不会再被执行顺序搞混。
3.1 小结对照表
| 类型 | 修饰符 | 执行时机 | 执行次数 |
|---|---|---|---|
| 静态代码块 | static | 类加载时 | 1次 |
| 构造代码块 | 无 | 对象创建、构造器前 | 每实例1次 |
通过上表可以直观区分二者。实际开发中,只要牢记类优先于对象、静态优先于实例这条主线,静态代码块与构造代码块的执行顺序就不再难以判断。