什么是 static 代码块?
static 代码块是 Java 中一种特殊的代码块,它被 static 关键字修饰,它的主要作用是在类被加载到 JVM(Java 虚拟机)时,自动执行一次,用于执行类的初始化操作。

它的语法格式如下:
static {
// 静态代码块中的代码
// 通常用于初始化静态变量
}
关键特性:
- 执行时机:在类加载阶段执行,当 JVM 第一次使用到这个类时(创建第一个对象、访问静态成员等),类的加载器会加载这个类的
.class文件,static代码块就会被执行。 - 执行次数:只执行一次,无论你创建了多少个该类的对象,或者访问了多少次该类的静态成员,
static代码块都只会执行一次。 - 执行顺序:按照在类中声明的顺序,从上到下依次执行。
- 访问权限:
static代码块可以访问类的静态成员(静态变量、静态方法),但不能直接访问实例成员(实例变量、实例方法),因为实例成员属于对象,而static代码块在对象创建之前就已经执行了。 - 与构造函数的区别:
static代码块在类加载时执行,而构造函数(constructor)在每次创建对象时都会执行。
代码示例
让我们通过几个例子来直观地理解 static 代码块的行为。
示例 1:基本用法和执行时机
public class StaticBlockDemo {
// 静态变量
static int staticVar;
// 实例变量
int instanceVar;
// 静态代码块 1
static {
System.out.println("静态代码块 1 被执行了!");
staticVar = 10; // 可以初始化静态变量
// 下面这行会编译错误,因为不能访问实例成员
// instanceVar = 20;
}
// 静态代码块 2
static {
System.out.println("静态代码块 2 被执行了!");
}
// 构造函数
public StaticBlockDemo() {
System.out.println("构造函数被执行了!");
this.instanceVar = 30;
}
public static void main(String[] args) {
System.out.println("--- main 方法开始 ---");
// 第一次创建对象,会触发类加载
System.out.println("创建第一个对象...");
StaticBlockDemo obj1 = new StaticBlockDemo();
// 第二次创建对象,类已经加载过,static 代码块不会再次执行
System.out.println("\n创建第二个对象...");
StaticBlockDemo obj2 = new StaticBlockDemo();
// 访问静态成员,同样不会触发类加载
System.out.println("\n访问静态变量 staticVar: " + StaticBlockDemo.staticVar);
System.out.println("--- main 方法结束 ---");
}
}
输出结果:

--- main 方法开始 ---
静态代码块 1 被执行了!
静态代码块 2 被执行了!
创建第一个对象...
构造函数被执行了!
创建第二个对象...
构造函数被执行了!
访问静态变量 staticVar: 10
--- main 方法结束 ---
分析:
- 当
main方法开始执行时,JVM 需要使用StaticBlockDemo类,因此开始加载该类。 - 加载类时,
static代码块按顺序执行,我们看到 "静态代码块 1" 和 "静态代码块 2" 的打印信息。 static代码块执行完毕后,main方法继续执行。- 创建
obj1时,构造函数被调用。 - 创建
obj2时,由于类已经加载过,static代码块不会再次执行,只有构造函数被调用。 - 访问静态成员
staticVar时,同样不会触发类加载。
示例 2:多个静态代码块和静态变量的初始化顺序
static 代码块和静态变量的初始化是按照它们在代码中出现的顺序进行的。
public class InitializationOrder {
static int a = 1;
static int b;
static {
System.out.println("进入第一个 static 代码块");
System.out.println("a = " + a); // a 已经被初始化为 1
System.out.println("b = " + b); // b 还未被显式初始化,默认为 0
b = 10;
System.out.println("b 被赋值为 10");
}
static int c = a + b; // 这行代码在 static 代码块之后执行
static {
System.out.println("进入第二个 static 代码块");
System.out.println("c = " + c); // c 此时已经被计算并赋值为 1 + 10 = 11
}
public static void main(String[] args) {
System.out.println("main 方法中 a=" + a + ", b=" + b + ", c=" + c);
}
}
输出结果:
进入第一个 static 代码块
a = 1
b = 0
b 被赋值为 10
进入第二个 static 代码块
c = 11
main 方法中 a=1, b=10, c=11
分析:

a = 1先执行。- 然后执行第一个
static代码块。b还是默认值0。 - 在第一个
static代码块中,b被赋值为10。 static变量c = a + b被执行,a=1,b=10,c=11。- 执行第二个
static代码块,c的值已经是11了。
static 代码块的实际应用场景
static 代码块最常见的用途就是初始化静态资源,这些资源通常在程序启动时就需要准备好,并且只需要初始化一次。
-
加载配置文件 当应用程序启动时,可能需要从配置文件(如
config.properties)中读取数据库连接信息、服务器地址等。static代码块是完成这项工作的理想位置。import java.io.InputStream; import java.util.Properties; public class ConfigLoader { private static Properties properties = new Properties(); // 在类加载时加载配置文件 static { try (InputStream input = ConfigLoader.class.getClassLoader().getResourceAsStream("config.properties")) { if (input == null) { throw new RuntimeException("无法找到 config.properties 文件"); } properties.load(input); System.out.println("配置文件加载成功!"); } catch (Exception e) { // 初始化失败,通常需要记录日志并终止程序 e.printStackTrace(); throw new RuntimeException("加载配置文件失败", e); } } // 提供一个静态方法来获取配置值 public static String getProperty(String key) { return properties.getProperty(key); } } -
初始化驱动或连接池 加载 JDBC 驱动程序,虽然现代 JDBC 驱动是自动加载的,但在一些旧代码或特定场景下,可能会手动加载。
public class DatabaseConnector { static { try { // 加载 MySQL 驱动 Class.forName("com.mysql.cj.jdbc.Driver"); System.out.println("JDBC 驱动加载成功!"); } catch (ClassNotFoundException e) { System.err.println("无法加载 JDBC 驱动!"); e.printStackTrace(); } } // ... 其他数据库连接逻辑 } -
执行一次性的计算或设置 根据复杂逻辑计算一个静态常量的值。
public class MathConstants { // 假设计算 pi 需要一个复杂且耗时的过程 public static final double PI; static { System.out.println("正在计算 PI 值..."); // 模拟一个复杂的计算过程 double calculatedPi = 4.0; for (int i = 1; i < 100000; i++) { double term = 1.0 / (2 * i + 1); if (i % 2 == 0) { calculatedPi += term; } else { calculatedPi -= term; } } PI = calculatedPi; System.out.println("PI 值计算并初始化完成。"); } }
最佳实践和注意事项
-
保持简洁:
static代码块应该只做与类初始化相关的简单工作,避免在其中执行耗时过长或复杂的逻辑,否则会拖慢应用程序的启动速度。 -
不要抛出未检查的异常:如果在
static代码块中抛出了未检查的异常(如RuntimeException),会导致java.lang.NoClassDefFoundError,该类将无法在当前 JVM 中被使用,这通常是致命的,应尽早处理异常(在try-catch块中记录日志并优雅地退出)。 -
替代方案:对于复杂的初始化逻辑,推荐使用一个静态的
initialize()方法,并在程序入口(如main方法)中显式调用它,这样可以更好地控制初始化过程,并提供更清晰的错误处理。// 更好的实践 public class Service { public static void initialize() { // 复杂的初始化逻辑 System.out.println("服务正在初始化..."); } } public class MainApplication { public static void main(String[] args) { try { Service.initialize(); // 显式调用 // ... 应用程序主逻辑 } catch (InitializationException e) { // 处理初始化失败 System.err.println("应用程序初始化失败,退出。"); System.exit(1); } } }
| 特性 | 描述 |
|---|---|
| 关键字 | static |
| 执行时机 | 类被 JVM 加载时 |
| 执行次数 | 仅一次 |
| 作用 | 初始化类的静态资源(如静态变量、加载配置文件、驱动等) |
| 访问权限 | 只能访问静态成员,不能访问实例成员 |
| 与构造函数区别 | static 代码块在类加载时执行;构造函数在每次创建对象时执行 |
static 代码块是 Java 提供给开发者,用于在类层面进行一次性初始化操作的强大工具。
