1. 引言
在 Java 编程中,我们经常使用构造方法来初始化对象,但实际上,除了构造方法,还有静态代码块(static block)和普通代码块(初始化块)这两种特殊的代码结构。它们也会在对象创建或类加载时执行,但执行顺序和次数有着严格的规则。本文将结合具体示例,详细讲解静态代码块、普通代码块与构造方法的执行机制,帮助你彻底理清它们之间的关系。
2. 静态代码块
2.1 定义
静态代码块使用static {}语法定义,属于类级别,与对象实例无关。
classDemo{static{System.out.println("静态代码块");}}2.2 执行时机
- 类加载时执行:当 JVM 首次加载某个类时,该类的静态代码块会被执行。
- 只执行一次:无论创建多少个该类的对象,静态代码块在整个程序生命周期中只执行一次。
- 静态代码块的执行早于
main方法:静态代码块在 JVM 调用main方法之前就已经执行完毕。
2.3 多个静态代码块的顺序
如果一个类中包含多个静态代码块,它们会按照在代码中书写的顺序依次执行。
classDemo{static{System.out.println("静态代码块 1");}static{System.out.println("静态代码块 2");}}// 输出:静态代码块 1 → 静态代码块 23. 普通代码块(构造代码块)
3.1 定义
普通代码块直接使用{}定义,没有static修饰符。它属于对象级别,常被称为构造代码块或初始化块。
classDemo{{System.out.println("普通代码块");}}3.2 执行时机
- 对象创建时执行:每创建一个该类的对象,普通代码块都会执行一次。
- 执行顺序:在构造方法之前执行。
- 编译后会被合并到构造方法中:从字节码层面看,普通代码块的内容会插入到每个构造方法的最前面(在
super()调用之后)。
3.3 与构造方法的区别
| 特性 | 普通代码块 | 构造方法 |
|---|---|---|
| 定义形式 | {} | 类名() {} |
| 是否可以有参数 | 否 | 是 |
| 执行顺序 | 先于构造方法 | 后于普通代码块 |
| 可重用性 | 所有构造方法共享同一段初始化逻辑 | 不同构造方法可以有不同实现 |
为了更直观地理解上述执行顺序,下面用一张流程图来描述静态代码块、普通代码块与构造方法在类加载与对象创建过程中的执行流程:
关键要点:类加载阶段只发生一次,在其中按书写顺序执行静态变量赋值和静态代码块;之后每创建一个对象,都会重复执行「普通代码块 → 构造方法」这一流程。
4. 构造方法
构造方法是对象创建的入口,用于完成对象的初始化工作。每创建一个对象,对应的构造方法就会被调用。当存在继承关系时,子类构造方法的第一条语句通常为super(),它会调用父类的构造方法。
构造方法的执行顺序:父类普通代码块 → 父类构造方法 → 子类普通代码块 → 子类构造方法(假设父类已有静态代码块,则父类静态代码块最先执行)。
5. 执行顺序验证
下面通过一个完整的示例来验证静态代码块、普通代码块和构造方法的执行顺序。
classDemo{publicDemo(){System.out.println("构造方法");}{System.out.println("代码块");}static{System.out.println("静态代码块");}}publicclassStaticAndVoidBlock{publicstaticvoidmain(String[]args){System.out.println("--- 第一次创建对象 ---");Demod1=newDemo();System.out.println("--- 第二次创建对象 ---");Demod2=newDemo();}}输出结果:
静态代码块 --- 第一次创建对象 --- 代码块 构造方法 --- 第二次创建对象 --- 代码块 构造方法结论:
- 静态代码块在程序开始运行、类加载时首先执行,且只执行一次。
- 每次创建对象时,先执行普通代码块,再执行构造方法。
- 第二次创建对象时,静态代码块不再执行,但普通代码块和构造方法仍会重复执行。
6. 深入理解:类加载与对象创建的完整生命周期
为了更清晰地理解上述机制,可以将整个流程拆解为两个阶段:
6.1 类加载阶段
- 加载:JVM 在第一次主动使用某个类时,从字节码中加载该类。
- 链接:验证、准备、解析。
- 初始化:执行静态变量赋值和静态代码块(按书写顺序)。此阶段线程安全,且只执行一次。
6.2 对象创建阶段
new Demo()指令触发对象创建。- 在堆内存中分配空间。
- 执行普通代码块(初始化块)。
- 执行构造方法体。
- 将对象引用赋给变量。
由此可见,静态代码块的static关键字使其提升到了类级别,而普通代码块则是实例级别。
7. 应用场景与注意事项
7.1 静态代码块的典型用途
- 加载静态资源:如读取配置文件、加载数据库驱动、初始化静态集合等。
- 单例模式的懒加载优化:利用静态内部类实现线程安全的单例。
- 执行一次性的前置检查:例如检查系统属性、密钥等。
classConfig{staticPropertiesprops=newProperties();static{try{props.load(Config.class.getResourceAsStream("/config.properties"));}catch(IOExceptione){thrownewRuntimeException(e);}}}7.2 普通代码块的典型用途
- 匿名内部类:匿名内部类没有名字,无法定义构造方法,此时只能用普通代码块来初始化。
- 简化构造方法:当多个构造方法存在相同的初始化逻辑时,可以将公共代码提取到普通代码块中,避免重复。
classBase{{System.out.println("公共初始化逻辑");}Base(){}Base(intx){}}7.3 注意事项
- 不要滥用静态代码块:过多的静态代码块会使类加载过程变得复杂,且可能引发不易察觉的初始化顺序问题。
- 避免在静态代码块中抛出未捕获的异常:这可能导致类加载失败,进而抛出
ExceptionInInitializerError。 - 子类与父类的顺序:父类的静态代码块 → 子类的静态代码块 → 父类的普通代码块 → 父类的构造方法 → 子类的普通代码块 → 子类的构造方法。如果存在多级继承,则按层级递推。
- 与静态变量初始化的关系:静态变量和静态代码块按书写顺序依次执行。
classDemo{staticinta=10;static{a=20;}}// 最终 a = 20,因为静态代码块在后面执行,覆盖了前面的赋值。8. 总结
- 静态代码块:类加载时执行,只执行一次,常用于静态资源的初始化。
- 普通代码块:每次创建对象时在构造方法之前执行,可用于共享初始化逻辑。
- 构造方法:每次创建对象时执行,负责对象的具体初始化,可以有参数和重载。
- 执行顺序:
静态代码块→普通代码块→构造方法(同一对象创建周期内)。
掌握这三者的区别和联系,不仅有助于理解 Java 对象的完整生命周期,也是面试中常见的考点。希望本文通过代码示例和原理剖析,能帮助你彻底理解并熟练运用它们。