在Java中,类的初始化(即执行类的<clinit>()
方法,该方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}
中的语句合并产生的)通常会在以下几种情况下被触发:
-
创建类的实例:当使用
new
关键字创建类的实例时,如果该类还没有被初始化,则会先触发该类的初始化。class MyClass { static { System.out.println("MyClass is being initialized."); } } public class Test { public static void main(String[] args) { MyClass obj = new MyClass(); // 触发MyClass的初始化 } }
-
访问类的静态变量(除了被
final
修饰的常量,且常量在编译期就能确定其值的情况):如果尝试访问类的静态变量,并且这个类还没有被初始化,那么会先触发该类的初始化。class MyClass { static int staticVar = 10; // 注意:这里不是编译时常量 static { System.out.println("MyClass is being initialized."); } } public class Test { public static void main(String[] args) { System.out.println(MyClass.staticVar); // 触发MyClass的初始化 } }
-
访问类的静态方法:当类的静态方法被调用时,如果该类还没有被初始化,则会先触发该类的初始化。
class MyClass { static { System.out.println("MyClass is being initialized."); } public static void staticMethod() { System.out.println("Static method called."); } } public class Test { public static void main(String[] args) { MyClass.staticMethod(); // 触发MyClass的初始化 } }
-
使用反射调用类的方法:通过反射(如
Class.forName("com.example.MyClass")
)来强制对类进行加载时,如果这个类还没有被初始化,则会触发该类的初始化。注意,Class.forName()
方法会触发类的加载、链接和初始化三个阶段。class MyClass { static { System.out.println("MyClass is being initialized."); } } public class Test { public static void main(String[] args) throws ClassNotFoundException { Class.forName("MyClass"); // 触发MyClass的初始化 } }
-
初始化一个类的子类:如果子类被初始化,而父类还没有被初始化,那么在初始化子类之前会先初始化父类。
class ParentClass { static { System.out.println("ParentClass is being initialized."); } } class ChildClass extends ParentClass { static { System.out.println("ChildClass is being initialized."); } } public class Test { public static void main(String[] args) { new ChildClass(); // 先触发ParentClass的初始化,然后触发ChildClass的初始化 } }
-
JVM启动时标明的启动类:JVM在启动时,会通过命令行参数指定一个主类(包含
main
方法的类),此时会触发该主类的初始化。 -
使用类加载器动态加载类:在使用类加载器(ClassLoader)动态加载类时,如果这个类还没有被初始化,则会触发该类的初始化。
需要注意的是,类的初始化只会被执行一次,因为<clinit>()
方法是同步的(synchronized),并且在类加载到JVM的过程中只会被调用一次。此外,静态初始化块(static blocks)按照它们在类中出现的顺序执行,并且只执行一次。
还需注意的是,对于final
修饰的静态常量,如果它们在编译时就能确定其值,那么这种常量的访问不会触发类的初始化。这是因为这些常量在编译时就已经被替换成了具体的值,而在运行时无需再引用到定义它们的类。