什么是 static 代码块?
static 代码块是 Java 中一种特殊的代码块,它使用 static 关键字修饰,并包含在一对大括号 中,它的主要作用是在类被加载到 JVM(Java 虚拟机)时,自动执行一次,用于执行类的初始化操作。
public class MyClass {
// 静态代码块
static {
System.out.println("MyClass 的静态代码块被执行了!");
}
// 构造方法
public MyClass() {
System.out.println("MyClass 的构造方法被执行了!");
}
}
static 代码块的核心特点
理解 static 代码块的关键在于掌握以下几个核心特点:
1 执行时机:类加载时
static 代码块在类被首次使用时,由 JVM 的类加载器负责执行,这里的“首次使用”包括:
- 创建该类的第一个实例(
new MyClass())。 - 访问该类的静态成员变量或静态方法(
MyClass.staticVar或MyClass.staticMethod())。 - 反射该类(
Class.forName("MyClass"))。
重要: 一个类只会被加载一次,static 代码块也只会被执行一次。
2 执行顺序:在构造方法之前
在一个类中,static 代码块的执行顺序总是早于构造方法,这是因为类的初始化(static 代码块执行)发生在任何对象创建(构造方法调用)之前。
3 执行顺序:在类中声明的顺序
如果一个类中有多个 static 代码块,它们会按照在类中声明的顺序依次执行。
4 访问权限
static 代码块内部可以:
- 访问类的静态成员变量(
static字段)。 - 调用类的静态方法(
static方法)。 - 不能访问非静态成员变量(实例字段)或非静态方法(实例方法),因为此时类的对象(实例)还没有被创建。
5 与静态变量的初始化
static 代码块和静态变量的初始化是紧密相关的,它们的执行顺序遵循“静态变量和静态代码块按声明顺序初始化”的原则。
public class InitializationOrder {
// 静态变量 1
static int a = 10;
// 静态代码块 1
static {
System.out.println("静态代码块 1 执行, a = " + a); // a 已经被赋值为 10
b = 20; // 可以给静态变量 b 赋值
// System.out.println(c); // 错误!静态变量 c 还未初始化,会编译报错
}
// 静态变量 2
static int b;
// 静态变量 3
static int c = 30;
public static void main(String[] args) {
System.out.println("main 方法开始");
System.out.println("a = " + a); // 10
System.out.println("b = " + b); // 20
System.out.println("c = " + c); // 30
}
}
执行结果:
静态代码块 1 执行, a = 10
main 方法开始
a = 10
b = 20
c = 30
为什么需要 static 代码块?典型应用场景
static 代码块主要用于以下场景:
1 初始化静态资源
这是最常见的用途,当你的类需要加载一些全局的、共享的资源时,可以使用 static 代码块来确保资源只被加载一次。
import java.sql.Connection;
import java.sql.DriverManager;
public class DatabaseConnector {
// 静态变量,保存数据库连接
private static Connection connection;
// 静态代码块,在类加载时初始化数据库连接
static {
try {
// 1. 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 创建连接(只创建一次)
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
System.out.println("数据库连接已成功建立!");
} catch (Exception e) {
e.printStackTrace();
// 在实际项目中,这里应该进行更优雅的错误处理,比如抛出一个运行时异常
throw new RuntimeException("无法初始化数据库连接", e);
}
}
// 提供一个获取连接的静态方法
public static Connection getConnection() {
return connection;
}
}
在这个例子中,DatabaseConnector 类需要一个全局唯一的数据库连接,使用 static 代码块可以确保 Connection 对象在类首次被使用时创建,并且只创建一次,避免了重复创建连接的开销。
2 执行只需要运行一次的复杂逻辑
有些代码逻辑比较复杂,只需要在程序启动时执行一次,之后不再需要。
- 从配置文件中读取大量配置项并加载到内存的静态
Map或Properties对象中。 - 注册一些全局的事件监听器或消息队列消费者。
import java.util.Properties;
public class ConfigLoader {
private static Properties properties = new Properties();
static {
// 模拟从配置文件加载
System.out.println("开始加载配置文件...");
try {
// properties.load(new FileInputStream("config.properties"));
// 为了演示,我们手动添加一些配置
properties.setProperty("app.name", "My Awesome App");
properties.setProperty("app.version", "1.0.0");
System.out.println("配置文件加载完成!");
} catch (Exception e) {
System.err.println("加载配置文件失败!");
}
}
public static String getConfig(String key) {
return properties.getProperty(key);
}
}
3 单例模式的一种实现方式
虽然更常见的是使用 private 构造方法和静态方法来实现单例,但 static 代码块也可以用来在类加载时就创建好单例实例。
public class EagerSingleton {
// 在类加载时就创建好实例
private static final EagerSingleton instance = new EagerSingleton();
// 私有构造方法,防止外部 new
private EagerSingleton() {
// 初始化代码
}
// 提供全局访问点
public static EagerSingleton getInstance() {
return instance;
}
}
这种方式被称为饿汉式单例,它的优点是简单、线程安全,缺点是无论是否使用,实例都会在类加载时被创建。
与实例初始化块()的对比
为了更好地理解 static 代码块,我们把它和普通的实例初始化块(也叫非静态代码块)放在一起比较。
| 特性 | static 代码块 (static { ... }) |
实例初始化块 () |
|---|---|---|
| 关键字 | static |
无 |
| 执行时机 | 类加载时,只执行一次。 | 每次创建对象时,都执行一次。 |
| 执行顺序 | 早于构造方法。 | 在构造方法之前执行。 |
| 访问权限 | 只能访问类的静态成员。 | 可以访问类的所有成员(静态和非静态)。 |
| 主要用途 | 初始化类的静态资源(全局配置、数据库连接等)。 | 提取多个构造方法中的公共初始化代码,避免代码重复。 |
实例初始化块示例:
public class Person {
private String name;
private int age;
// 实例初始化块
{
System.out.println("实例初始化块被执行了!");
// 可以访问实例变量
this.name = "默认姓名";
}
public Person() {
System.out.println("Person 的无参构造方法被执行了!");
}
public Person(int age) {
this.age = age;
System.out.println("Person 的带参构造方法被执行了!");
}
public static void main(String[] args) {
System.out.println("--- 创建第一个对象 ---");
Person p1 = new Person(); // 实例初始化块和构造方法都会执行
System.out.println("\n--- 创建第二个对象 ---");
Person p2 = new Person(25); // 实例初始化块和构造方法都会再次执行
}
}
执行结果:
--- 创建第一个对象 ---
实例初始化块被执行了!
Person 的无参构造方法被执行了!
--- 创建第二个对象 ---
实例初始化块被执行了!
Person 的带参构造方法被执行了!
static代码块是 Java 类的一部分,用于执行类的初始化操作。- 它在类被加载时由 JVM 自动执行,且只执行一次。
- 它的执行顺序在构造方法之前。
- 主要用途是初始化静态资源,如全局配置、数据库连接、驱动加载等。
- 它与实例初始化块 () 的最大区别在于执行时机和访问权限:
static代码块作用于类,而实例初始化块作用于每个对象。
理解 static 代码块对于掌握 Java 的类加载机制和资源管理至关重要,是 Java 程序员必须掌握的基础知识。
