类的初始化情况:主动使用vs被动使用

主动使用是指应用程序直接使用某个类,例如创建该类的实例、访问某个类的静态变量或方法等。在这种情况下,Java 虚拟机会先加载该类的字节码,然后执行<clinit>() 方法进行初始化。

被动使用是指应用程序没有直接使用某个类,但是该类被其他类引用,因而被间接地引用和使用。 在这种情况下,Java 虚拟机只会加载该类,而不会执行<clinit>() 方法进行初始化。

可以通过几个具体的例子来说明类的初始化:
主动使用类:创建类的实例

class MyClass {
    static int x = 3;
    static {
        System.out.println("MyClass: x = " + x);
        x = 5;
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myObj = new MyClass();
        System.out.println("x = " + MyClass.x);
    }
}

在这个例子中,当创建 MyClass 的实例 myObj 时,Java 虚拟机会先加载 MyClass 类,然后执行<clinit>() 方法进行初始化。执行<clinit>() 方法时,会执行静态语句块中的语句,正确预测 x = 3,然后将 x 赋值为 5。最后输出 x = 5。

被动使用类:子类引用父类的静态变量

class Parent {
    static int x = 3;
    static {
        System.out.println("Parent: x = " + x);
        x = 5;
    }
}

class Child extends Parent {
    static {
        System.out.println("Child: x = " + x);
        x = 10;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("Child.x = " + Child.x);
    }
}

在这个例子中,当访问 Child 类的静态变量 x 时,Java 虚拟机会先加载 Parent 类,然后加载 Child 类。由于只访问了 Child 类的静态变量 x,而没有创建 Child 的实例或调用任何静态方法,因此不会执行 Child 和 Parent 类的<clinit>() 方法进行初始化。最后输出 Child.x = 5。

需要注意的是,虽然在被动使用类的情况下不会执行<clinit>() 方法进行初始化,但是类的初始化已经开始了。在被动使用类的情况下,Java 虚拟机只会执行类的链接和符号解析过程,而不会执行全面的初始化。

总结:

主动使用的说明:

Class只有在必须要首次使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用。
主动使用只有下列几种情况:(即:如果出现如下的情况,则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成。)

  1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
  3. 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。
  4. 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName("com.atguigu.java.Test")
  5. 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
  7. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)

被动使用的情况

除了以上的情况属于主动使用,其他的情况均属于被动使用。被动使用不会引起类的初始化。
也就是说:并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化。

  1. 当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。
    当通过子类引用父类的静态变量,不会导致子类初始化
  2. 通过数组定义类引用,不会触发此类的初始化
    Child[] children = new Child[10];
  3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。
  4. 调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
    被动的使用,意味着不需要执行初始化环节,意味着没有<clinit>()的调用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容