小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-08 / 0 阅读
0
0

JAVA静态代码块

好的,我们来详细讲解 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("静态代码块执行");
}

① 在 ② 之前,于是:

  1. 类被触发加载(main 方法在这个类里,启动时就会加载)。
  2. 收集所有静态赋值与静态代码块,按 源码顺序 放进 <clinit> 方法:
    • 先执行 initStaticVar() → 打印 “静态变量初始化”
    • 再执行静态代码块 → 打印 “静态代码块执行”
  3. <clinit> 执行完后,类加载完成,JVM 继续执行 main 的字节码,打印 “main方法开始”。
  4. 遇到 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,再跑一次,可验证:

  1. 父类 <clinit> 先于子类 <clinit>
  2. 父类 <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 结束

观察要点(对应你的问题“如果我调用其他类,怎么弄?”)

  1. 调用方 MainTrigger 自身没有静态成员,所以它自己的 <clinit> 为空,看不到任何“静态初始化”输出。
  2. 直到 main 里第一次访问 OtherClass.staticValue,JVM 才加载并初始化 OtherClass——此时才出现 B → C
  3. 之后 new OtherClass() 只是对象创建,再走一遍 D → E → F;静态阶段(B、C)不会再跑
  4. 如果把 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 的时机。

结论:
没有“访问静态变量”这一步,类加载不会被提前;只要程序里任何“首次主动使用”点都能触发加载,只是时机不同。

总结访问顺序

  1. 类加载阶段只负责 静态级别<clinit>):
    静态变量初始化 + 静态代码块,按源码顺序执行一次
    静态方法只是被装载并解析,不执行。
  2. 对象创建阶段只负责 实例级别<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 一次

静态方法从不自动执行,只有被显式调用时才执行。


评论