杰瑞科技汇

static代码块在Java中何时执行?

什么是 static 代码块?

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

static代码块在Java中何时执行?-图1
(图片来源网络,侵删)

它的语法格式如下:

static {
    // 静态代码块中的代码
    // 通常用于初始化静态变量
}

关键特性:

  1. 执行时机:在类加载阶段执行,当 JVM 第一次使用到这个类时(创建第一个对象、访问静态成员等),类的加载器会加载这个类的 .class 文件,static 代码块就会被执行。
  2. 执行次数只执行一次,无论你创建了多少个该类的对象,或者访问了多少次该类的静态成员,static 代码块都只会执行一次。
  3. 执行顺序:按照在类中声明的顺序,从上到下依次执行。
  4. 访问权限static 代码块可以访问类的静态成员(静态变量、静态方法),但不能直接访问实例成员(实例变量、实例方法),因为实例成员属于对象,而 static 代码块在对象创建之前就已经执行了。
  5. 与构造函数的区别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 方法结束 ---");
    }
}

输出结果:

static代码块在Java中何时执行?-图2
(图片来源网络,侵删)
--- main 方法开始 ---
静态代码块 1 被执行了!
静态代码块 2 被执行了!
创建第一个对象...
构造函数被执行了!
创建第二个对象...
构造函数被执行了!
访问静态变量 staticVar: 10
--- main 方法结束 ---

分析:

  1. main 方法开始执行时,JVM 需要使用 StaticBlockDemo 类,因此开始加载该类。
  2. 加载类时,static 代码块按顺序执行,我们看到 "静态代码块 1" 和 "静态代码块 2" 的打印信息。
  3. static 代码块执行完毕后,main 方法继续执行。
  4. 创建 obj1 时,构造函数被调用。
  5. 创建 obj2 时,由于类已经加载过,static 代码块不会再次执行,只有构造函数被调用。
  6. 访问静态成员 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

分析:

static代码块在Java中何时执行?-图3
(图片来源网络,侵删)
  1. a = 1 先执行。
  2. 然后执行第一个 static 代码块。b 还是默认值 0
  3. 在第一个 static 代码块中,b 被赋值为 10
  4. static 变量 c = a + b 被执行,a=1, b=10c=11
  5. 执行第二个 static 代码块,c 的值已经是 11 了。

static 代码块的实际应用场景

static 代码块最常见的用途就是初始化静态资源,这些资源通常在程序启动时就需要准备好,并且只需要初始化一次。

  1. 加载配置文件 当应用程序启动时,可能需要从配置文件(如 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);
        }
    }
  2. 初始化驱动或连接池 加载 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();
            }
        }
        // ... 其他数据库连接逻辑
    }
  3. 执行一次性的计算或设置 根据复杂逻辑计算一个静态常量的值。

    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 提供给开发者,用于在类层面进行一次性初始化操作的强大工具。

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