静态块真的是类加载的时候执行么?

本文通过两个示例探讨了Java中静态块的执行时机,并解释了不同加载方式导致的执行差异。强调静态块通常在首次实例化时执行,并澄清了initialize参数对静态变量赋值的影响。

最近在读java深度历险这本书,书中第二章有个关于静态块执行时机的讨论,这一改我以前的认知啊。

很多博客或是书籍都认为静态块是类加载的时候执行的,真相真的是这样么?

我们先来看两个例子:

example 1: 

public class Test {

    public static void main(String[] args) throws Exception {
        System.out.println("initialize...");
        Class c = Class.forName("TestStatic");
        System.out.println("instance...");
        Object obj1 = c.newInstance();
        Object obj2 = c.newInstance();
        System.out.println("end ...");
    }
}
public class TestStatic {
    static {
        System.out.println("*******************");
    }
}

输出结果:

这次结果是TestStatic的静态块是在类加载的时候执行的,接下来我们看看另一个例子

example 2:

public class Test {

    public static void main(String[] args) throws Exception {
        System.out.println("initialize...");
        Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());
        System.out.println("instance...");
        Object obj1 = c.newInstance();
        Object obj2 = c.newInstance();
        System.out.println("end ...");
    }
}

输出结果:

这次我们看到静态块是在实例化的时候执行的,为何会造成这两种截然不同的结果呢?

原因在于两次使用了不同的类加载方法

Class c = Class.forName("TestStatic");
Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());

看看源码可以发现两种类加载方式最终调用的都是一个native方法forName0()

第三个参数ClassLoader都是代码执行者Test类的ClassLoader,由此可以看出两次静态块执行时机的不同就是一个参数initialize值决定的。同时,我们看到即使是后面再实例化了一个obj2,static块也没有再次执行,这表明静态块仅仅在第一次实例化才会执行。

===============================这段测试有问题======================================

关于参数initialize的含义我看到网上有博客说就是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块,以及对静态变量的赋值等操作联系类加载的过程相关知识,我们知道给静态变量赋初始值是在“准备”阶段,真正赋上代码中的值“9”在“初始化”阶段(final修饰的静态变量赋值“9”在“准备”阶段),开始我以为设置为false就可以跳过类加载的“初始化”步骤,但测试过后发现这个参数initialize并不可以控制对静态变量的赋值。

测试:

TestStatic类中新增static变量i,赋值9

public class TestStatic {
    static int i = 9;
    static {
        System.out.println("*******************");
    }
}

Test类设置initialize为false的加载方式,同时在加载过后以及实例化过后新增打印语句:

public class Test {

    public static void main(String[] args) throws Exception {
        System.out.println("initialize...");
        Class c = Class.forName("TestStatic", false, Test.class.getClassLoader());
        System.out.println("1. i = " + TestStatic.i);
        System.out.println("instance...");
        Object obj = c.newInstance();
        System.out.println("2. i = " + TestStatic.i);
        System.out.println("end ...");
    }
}

然而,结果是加载过后实例化之前静态变量i的赋值动作已经完成,initialize参数为false并没有控制住静态变量的赋值动作。

不知道是不是我测试的方式有问题,如果哪位朋友知道请告知下,感激不尽!

===============================================================================================

综上,静态块实际是在第一次实例化的时候才会执行,只不过我们在日常写代码时使用的都是initialize为true的类加载方式,所以会产生静态块是类加载时执行的错误认知。

以上如果有理解不对的地方还望大家能够指正和包容,谢谢!

********************************************************************************************************************

已经有大神指出这个测试方式有问题,对于类名.静态变量这种取值方式,JVM会立即对该类做类加载的初始化动作的,所以此时测试出来的“9”并不能推导出initialize参数为false没有控制住静态变量的赋值动作。

这段话摘自《深入理解Java虚拟机第2版》第七章中的类加载的时机的内容:
虚拟机规范规定了,当遇到new、getstatic、putstatic或invokestatic这4条字节码指令是,如果没有进行过初始化则需要先触发初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或者设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

非常感谢指出错误的朋友,以免误导大家!

<think>我们正在讨论静态代码块与类加载执行顺序问题。 根据引用[1]:类加载执行<clinit>方法,该方法由编译器自动收集类中的所有静态变量赋值和静态代码块语句合并而成,按源码顺序执行。因此,静态代码块是在类加载过程中执行的。 引用[3]进一步说明:静态代码块在类加载可以执行,但不一定会执行。这取决于类加载的方式。例如: - 使用Class.forName("类名")默认会执行静态代码块(初始化阶段会执行<clinit>) - 使用ClassLoader.loadClass("类名")不会执行静态代码块(只加载,不连接阶段的初始化) 所以,静态代码块是类加载过程中的初始化阶段(Initialization)执行的,它是类加载的一个步骤。因此,我们不能简单地说静态代码块和类加载哪个先执行,因为静态代码块是类加载过程中的一部分。 具体来说,类加载分为加载(Loading)、连接(Linking,包括验证、准备、解析)和初始化(Initialization)几个阶段。静态代码块和静态变量的初始化赋值都是在初始化阶段执行的。 结论:类加载过程中,初始化阶段会执行静态代码块。因此,没有“静态代码块和类加载哪个先执行”的说法,因为静态代码块是类加载的一个子步骤(在初始化阶段执行)。当类加载进行到初始化阶段,就会执行静态代码块(如果该类被主动使用,导致初始化发生)。 但是,需要注意的是,类加载并不一定会触发初始化(如引用[3]所示,不同的加载方式可能导致初始化阶段不被执行,因此静态代码块也不会执行)。 下面用一个简单的顺序描述: 1. 加载(Loading):将类的字节码加载到内存中。 2. 连接(Linking): - 验证(Verification) - 准备(Preparation):为静态变量分配内存并设置默认初始值(0, false, null等)。 - 解析(Resolution):将符号引用转换为直接引用。 3. 初始化(Initialization):执行静态变量的显式初始化和静态代码块,按源码顺序执行。 因此,静态代码块是在类加载的最后阶段(初始化)执行的。如果类加载没有进行到初始化阶段(比如只加载而不初始化),则静态代码块不会执行。 为了更清晰地说明,我们参考引用[2]中的例子: class ClassTest { static final ClassTest instance = new ClassTest(); static { System.out.println("执行了静态代码块"); } public ClassTest() { System.out.println("执行了构造方法"); } } 当这个类被初始化: - 首先,初始化静态变量。这里有一个静态变量instance,它的初始化是通过new一个对象,所以会调用构造方法。此构造方法执行,输出“执行了构造方法”。 - 然后,执行静态代码块,输出“执行了静态代码块”。 注意:初始化阶段是按顺序执行静态变量赋值和静态代码块的。所以这里先执行静态变量instance的赋值(调用构造方法),再执行静态代码块。 输出结果为: 执行了构造方法 执行了静态代码块 但是,如果改变静态变量和静态代码块的顺序: static { System.out.println("执行了静态代码块"); } static final ClassTest instance = new ClassTest(); 那么输出就会变成: 执行了静态代码块 执行了构造方法 这印证了初始化阶段是按源码顺序执行。 另外,引用[5]展示了继承关系中的执行顺序: - 首先,父类的静态代码块(类加载只一次) - 然后,子类的静态代码块(类加载只一次) - 创建对象(new): - 父类的实例代码块 - 父类的构造方法 - 子类的实例代码块 - 子类的构造方法 总结:静态代码块是类加载过程中初始化阶段的一部分,因此它是在类加载的特定阶段(初始化阶段)执行的。 </think>静态代码块是类加载过程中的一个组成部分,具体执行顺序取决于类加载的阶段和技术细节。以下是对两者关系的系统分析: ### 一、静态代码块与类加载的关系 静态代码块是类加载**初始化阶段**的核心操作: 1. **类加载过程**: - 加载(Loading)→ 连接(Linking)→ 初始化(Initialization) - 静态代码块在**初始化阶段**执行(`<clinit>`方法) 2. **执行条件**: - 仅当类被**主动使用**触发初始化(如 `new` 实例化、访问静态成员等) - 被动引用(如定义`Class`变量)不会触发静态代码块[^3] ### 二、静态代码块的执行特性 1. **执行时机**: $$ \text{类加载} \rightarrow \text{初始化阶段} \rightarrow \text{静态代码块执行} $$ - 在静态变量显式初始化后执行(按源码顺序)[^4] 2. **控制因素**: ```java // 方式1:触发静态代码块(默认初始化) Class.forName("com.example.ClassA"); // 方式2:禁止执行静态代码块 Class.forName("com.example.ClassA", false, loader); ``` 通过`ClassLoader.loadClass()`加载类不会触发静态代码块[^3] 3. **继承场景顺序**: ```mermaid graph LR A[父类静态代码块] --> B[子类静态代码块] B --> C[父类实例代码块] C --> D[父类构造方法] D --> E[子类实例代码块] E --> F[子类构造方法] ``` 静态代码块仅在首次类加载执行一次[^5] ### 三、关键结论 1. **先后关系**:类加载的初始化阶段 **包含** 静态代码块执行 2. **必要条件**:静态代码块执行需满足: - 类被主动使用 - 类加载过程完成初始化阶段 3. **顺序控制**:静态代码块与静态变量按源码顺序执行[^1][^4]: ```java public class Demo { static int id = 1; // 静态变量 static { System.out.println(id); // 输出1 id = 2; // 修改值 } } ``` ### 四、典型场景验证 ```java class Test { static { System.out.println("静态代码块执行"); } } public class Client { public static void main(String[] args) throws Exception { ClassLoader loader = ClassLoader.getSystemClassLoader(); loader.loadClass("Test"); // 无输出(不触发初始化) Class.forName("Test"); // 输出"静态代码块执行" } } ``` 证明静态代码块执行严格依赖于初始化阶段的触发[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值