好的,我们来详细讲解 Java 中的静态代码块。
1. 什么是静态代码块?
静态代码块是使用 static
关键字修饰的代码块,它在类加载到内存时执行,并且只执行一次。
语法:
static {
// 静态代码块中的代码
System.out.println("静态代码块执行了");
}
2. 执行时机和特点
关键特性:
- 执行时机:在类被首次加载时自动执行
- 执行次数:在整个程序运行期间只执行一次
- 执行顺序:早于构造函数、普通代码块和任何实例方法
- 与对象无关:静态代码块的执行不需要创建类的实例对象
3. 详细示例
基础示例:
public class StaticBlockExample {
// 静态变量
static String className;
// 静态代码块
static {
className = "StaticBlockExample";
System.out.println("静态代码块执行:初始化className为 " + className);
}
// 构造函数
public StaticBlockExample() {
System.out.println("构造函数执行");
}
public static void main(String[] args) {
System.out.println("main方法开始");
// 第一次创建对象
System.out.println("创建第一个对象:");
StaticBlockExample obj1 = new StaticBlockExample();
// 第二次创建对象
System.out.println("创建第二个对象:");
StaticBlockExample obj2 = new StaticBlockExample();
}
}
输出结果:
静态代码块执行:初始化className为 StaticBlockExample
main方法开始
创建第一个对象:
构造函数执行
创建第二个对象:
构造函数执行
执行顺序验证:
public class ExecutionOrder {
// 静态变量
static int staticVar = initStaticVar();
// 静态代码块
static {
System.out.println("静态代码块执行");
}
// 普通代码块
{
System.out.println("普通代码块执行");
}
// 构造函数
public ExecutionOrder() {
System.out.println("构造函数执行");
}
// 静态方法
private static int initStaticVar() {
System.out.println("静态变量初始化");
return 100;
}
public static void main(String[] args) {
System.out.println("main方法开始");
new ExecutionOrder();
}
}
输出结果:
静态变量初始化
静态代码块执行
main方法开始
普通代码块执行
构造函数执行
因为类加载阶段「先初始化静态成员」和「再执行静态代码块」这两个动作,在 同一个 <clinit>
方法里按源代码顺序完成。
你的代码里:
static int staticVar = initStaticVar(); // ①
static { // ②
System.out.println("静态代码块执行");
}
① 在 ② 之前,于是:
- 类被触发加载(
main
方法在这个类里,启动时就会加载)。 - 收集所有静态赋值与静态代码块,按 源码顺序 放进
<clinit>
方法:- 先执行
initStaticVar()
→ 打印 “静态变量初始化” - 再执行静态代码块 → 打印 “静态代码块执行”
- 先执行
<clinit>
执行完后,类加载完成,JVM 继续执行main
的字节码,打印 “main方法开始”。- 遇到
new ExecutionOrder()
,进入实例创建阶段:- 先为对象分配内存、默认清零字段;
- 执行实例初始化代码块(
{}
)→ 打印 “普通代码块执行”; - 再执行构造函数本体 → 打印 “构造函数执行”。
因此你看到的结果就是:
静态变量初始化
静态代码块执行
main方法开始
普通代码块执行
构造函数执行
如果把静态变量声明和静态代码块的顺序调换,那么两条输出也会跟着调换,其余不变。
4. 多个静态代码块的执行顺序
如果有多个静态代码块,它们会按照在代码中出现的先后顺序执行:
public class MultipleStaticBlocks {
static {
System.out.println("第一个静态代码块");
}
static String name = initName();
static {
System.out.println("第二个静态代码块");
System.out.println("name = " + name);
}
private static String initName() {
System.out.println("初始化静态变量name");
return "Java";
}
public static void main(String[] args) {
System.out.println("main方法执行");
}
}
输出结果:
第一个静态代码块
初始化静态变量name
第二个静态代码块
name = Java
main方法执行
5. 实际应用场景
场景1:加载配置文件或资源
public class DatabaseConfig {
private static Properties config;
static {
System.out.println("正在加载数据库配置...");
config = new Properties();
try {
// 加载配置文件
config.load(DatabaseConfig.class
.getResourceAsStream("/database.properties"));
System.out.println("数据库配置加载完成");
} catch (IOException e) {
System.err.println("加载配置文件失败: " + e.getMessage());
}
}
public static String getConfig(String key) {
return config.getProperty(key);
}
}
场景2:初始化静态容器
public class CacheManager {
private static Map<String, Object> cache;
static {
System.out.println("初始化缓存容器...");
cache = new HashMap<>();
// 预加载一些数据
cache.put("default_timeout", 300);
cache.put("max_connections", 100);
}
public static void put(String key, Object value) {
cache.put(key, value);
}
public static Object get(String key) {
return cache.get(key);
}
}
场景3:注册驱动(经典案例)
public class JDBCExample {
static {
try {
// 注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("MySQL驱动注册成功");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("找不到数据库驱动");
}
}
}
6. 继承关系中的静态代码块
在继承体系中,静态代码块的执行遵循父类先于子类的原则:
class Parent {
static {
System.out.println("父类静态代码块");
}
}
class Child extends Parent {
static {
System.out.println("子类静态代码块");
}
}
public class TestInheritance {
public static void main(String[] args) {
System.out.println("第一次创建子类对象:");
new Child();
System.out.println("第二次创建子类对象:");
new Child();
}
}
输出结果:
父类静态代码块
子类静态代码块
第一次创建子类对象:
第二次创建子类对象:
7. 总结
特性 | 静态代码块 | 构造函数 |
---|---|---|
执行时机 | 类加载时 | 创建对象时 |
执行次数 | 只执行一次 | 每次创建对象都执行 |
执行顺序 | 先执行 | 后执行 |
依赖关系 | 不依赖对象实例 | 依赖对象实例 |
核心要点:
- 静态代码块在类加载后、对象创建前执行
- 主要用于一次性初始化静态资源
- 执行早于构造函数,且只执行一次
- 多个静态代码块按定义顺序执行
- 在继承中,父类的静态代码块先于子类执行
静态代码块是Java中实现类级别初始化的强大工具,特别适合用于加载配置、注册驱动、初始化静态资源等场景。
下面给出“实例版”笔记:
把「理论顺序」和「真实代码 + 断点输出」一一对应,方便直接拷贝运行、验证记忆。
Java 初始化顺序实例笔记
一、实验代码(单类,含静态/实例/构造)
public class InitDemo {
/* ---------- 1. 静态区域 ---------- */
static int staticVar = printAndInit("① 静态变量初始化");
static {
print("② 静态代码块");
}
/* ---------- 2. 实例区域 ---------- */
int instanceVar = printAndInit("④ 实例变量初始化");
{
print("③ 实例代码块(先于变量!)");
}
/* ---------- 3. 构造 ---------- */
public InitDemo() {
print("⑤ 构造函数");
}
/* ---------- 4. main ---------- */
public static void main(String[] args) {
print("=== main 开始 ===");
new InitDemo(); // 第一次 new
print("-------------");
new InitDemo(); // 第二次 new,观察静态部分是否再跑
}
/* ---------- 工具 ---------- */
private static int printAndInit(String msg) {
print(msg);
return 88;
}
private static void print(String msg) {
System.out.println(msg);
}
}
二、运行结果(拷贝即可复现)
① 静态变量初始化
② 静态代码块
=== main 开始 ===
③ 实例代码块(先于变量!)
④ 实例变量初始化
⑤ 构造函数
-------------
③ 实例代码块(先于变量!)
④ 实例变量初始化
⑤ 构造函数
三、对照记忆表
阶段 | 输出编号 | 是否只跑一次 | 备注 |
---|---|---|---|
类加载 | ①② | ✅ | 按源码顺序收集 |
第一次 new | ③④⑤ | ❌ | 每 new 一次就跑一次 |
第二次 new | ③④⑤ | ❌ | 静态阶段不再出现 |
四、继承场景补充(留作作业)
把上面代码拆成父类 Parent
与子类 Child
,再跑一次,可验证:
- 父类
<clinit>
先于子类<clinit>
- 父类
<init>
先于子类<init>
五、一句话总结
静态按源码顺序只一次,实例每次 new 都从代码块→变量→构造走一遍;父类永远先行。
调用第三方类
下面给你演示「主动调用另外一个类」时,初始化顺序到底怎么走。
一共两个 .java
文件,复制即可跑通,每句输出都带编号,一眼就能对上笔记。
文件 1:入口类(没有任何静态成员,只做“触发”)
public class MainTrigger {
public static void main(String[] args) {
System.out.println("1. main 开始");
// 第一次“主动使用”OtherClass
System.out.println("2. 准备访问 OtherClass.staticValue");
int x = OtherClass.staticValue; // 触发类加载
System.out.println("3. 准备 new OtherClass()");
new OtherClass(); // 只触发实例流程,静态不再执行
System.out.println("4. main 结束");
}
}
文件 2:被调用的类(带完整静态/实例/构造)
public class OtherClass {
/* ---------- 静态区域 ---------- */
static int staticValue = printAndInit("B. 静态变量");
static {
System.out.println("C. 静态代码块");
}
/* ---------- 实例区域 ---------- */
int ins = printAndInit("E. 实例变量");
{
System.out.println("D. 实例代码块(源码顺序在变量前)");
}
/* ---------- 构造 ---------- */
public OtherClass() {
System.out.println("F. 构造函数");
}
/* ---------- 工具 ---------- */
private static int printAndInit(String msg) {
System.out.println(msg);
return 99;
}
}
编译 & 运行
javac MainTrigger.java OtherClass.java
java MainTrigger
控制台输出(编号已对齐)
1. main 开始
2. 准备访问 OtherClass.staticValue
B. 静态变量
C. 静态代码块
3. 准备 new OtherClass()
D. 实例代码块(源码顺序在变量前)
E. 实例变量
F. 构造函数
4. main 结束
观察要点(对应你的问题“如果我调用其他类,怎么弄?”)
- 调用方
MainTrigger
自身没有静态成员,所以它自己的<clinit>
为空,看不到任何“静态初始化”输出。 - 直到
main
里第一次访问OtherClass.staticValue
,JVM 才加载并初始化OtherClass
——此时才出现 B → C。 - 之后
new OtherClass()
只是对象创建,再走一遍 D → E → F;静态阶段(B、C)不会再跑。 - 如果把
OtherClass
里的静态变量/代码块顺序调换,输出顺序会跟着变,再次验证“按源码顺序收集”规则。
一句话总结
“跨类调用”初始化顺序不变:
先被调用类的静态(按源码顺序,只一次) → 后被调用类的实例(每 new 一次走一遍)。
入口类自身有没有静态成员,只决定它自己是否额外输出一段 <clinit>
而已。
去掉这一行:
// int x = OtherClass.staticValue; // 触发类加载
结果会变成:
1. main 开始
3. 准备 new OtherClass()
B. 静态变量
C. 静态代码块
D. 实例代码块(源码顺序在变量前)
E. 实例变量
F. 构造函数
4. main 结束
解释:
- 第一次 主动使用
OtherClass
的语句变成了new OtherClass()
。 - 因此 类加载 被推迟到第一次
new
时才发生,静态初始化(B → C)也跟着往后挪。 - 其余顺序不变,只是“静态初始化”从 访问静态变量 挪到了 第一次 new 的时机。
结论:
没有“访问静态变量”这一步,类加载不会被提前;只要程序里任何“首次主动使用”点都能触发加载,只是时机不同。
总结访问顺序
- 类加载阶段只负责 静态级别(
<clinit>
):
静态变量初始化 + 静态代码块,按源码顺序执行一次;
静态方法只是被装载并解析,不执行。 - 对象创建阶段只负责 实例级别(
<init>
):
实例代码块 → 实例变量初始化 → 构造函数本体,按源码顺序拼成一个<init>
方法,每 new 一次就跑一次。
public class LoadOrder {
/* ====== 静态区域 ====== */
static int sa = print("1. 静态变量 sa");
static {
print("2. 静态代码块");
}
static int sb = print("3. 静态变量 sb");
static void sm() { print("静态方法 sm 被调用"); } // 只装载,不执行
/* ====== 实例区域 ====== */
int ia = print("6. 实例变量 ia");
{
print("5. 实例代码块");
}
int ib = print("7. 实例变量 ib");
/* ====== 构造 ====== */
LoadOrder() {
print("8. 构造函数");
}
/* ====== 入口 ====== */
public static void main(String[] args) {
print("4. main 开始 —— 类加载已完成");
sm(); // 手动调用静态方法
print("--------- 第一次 new ---------");
new LoadOrder();
print("--------- 第二次 new ---------");
new LoadOrder();
}
private static int print(String msg) {
System.out.println(msg);
return 0;
}
}
顺序:
1. 静态变量 sa
2. 静态代码块
3. 静态变量 sb
4. main 开始 —— 类加载已完成
静态方法 sm 被调用
--------- 第一次 new ---------
5. 实例代码块
6. 实例变量 ia
7. 实例变量 ib
8. 构造函数
--------- 第二次 new ---------
5. 实例代码块
6. 实例变量 ia
7. 实例变量 ib
8. 构造函数
类加载:只跑静态变量 + 静态代码块,按源码顺序,一次。
对象创建:实例代码块 → 实例变量 → 构造函数,按源码顺序,每 new 一次。
静态方法从不自动执行,只有被显式调用时才执行。