杰瑞科技汇

Java static 代码块执行时机是什么?

什么是 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.staticVarMyClass.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 执行只需要运行一次的复杂逻辑

有些代码逻辑比较复杂,只需要在程序启动时执行一次,之后不再需要。

  • 从配置文件中读取大量配置项并加载到内存的静态 MapProperties 对象中。
  • 注册一些全局的事件监听器或消息队列消费者。
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 程序员必须掌握的基础知识。

分享:
扫描分享到社交APP
上一篇
下一篇