在类或接口被加载的时机上,Java虚拟机规范给实现提供的一定的灵活性,但是又严格定义了初始化的时机,所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化它们。Java程序对类的使用可分为两种:主动使用和被动使用,在下面的类的初始化时机进行详细阐述。
三、类的初始化时机
JVM只有在程序首次主动使用一个类或接口时才会初始化它。只有以下6种方式被看作程序对类或接口的主动使用。
01.创建类的实例。包括new关键字来创建,或者通过反射、克隆及反序列化方式来创建实例。

02.调用类的静态方法。
03.访问某个类或接口的静态变量,或者对该静态变量赋值。
04.使用反射机制来创建某个类或接口对应的java.lang.Class对象。例如Class.forName("Test")操作,如果系统还未初始化Test类,这波操作会导致该Test类被初始化,并返回Test类对应的java.lang.Class对象。
05.初始化一个类的子类,该子类所有的父类都会被初始化。
06.JVM启动时被标明为启动类的类(直接使用java.exe命令运行某个主类)。例如对于“java Test”命令,Test类就是启动类(主类),JVM会先初始化这个主类。
除了以上6种情况,其他方式都被看作成是 被动使用,不会导致类的初始化。下面通过接个例子来验证:
“宏变量”:
1.对于final类型的静态变量,如果在编译时就能计算出变量的取值,那么这种变量看作 编译时常量。Java程序中对类的编译时常量的使用,被看作是对类的被动使用,不会导致类的初始化。
上面例子的因为编译时能计算出a为6,所以程序访问A.a时,是对A类的被动使用,不会导致A类初始化。
在Test测试类中运行程序:控制台只打印出6,并没有打印静态代码块中的 init A。
当Java编译器生成A类的class文件时,他不会在main()方法的字节码流中保存一个表示“A.a”的符号引用,而是直接在字节码流中嵌入常量值6。因此当程序访问A.a时,客观上无须初始化A类。(当JVM加载并连接A类时,不会在方法区内为它的编译时常量a分配内存。)
2.对于final类型的静态变量,如果在编译时不能计算出变量的取值,那么程序对类的这种变量的使用,被看作是对类的主动使用,会导致类的初始化
访问A类中不是编译时常量的b,控制台会打印出 init A 4。
这波操作JVM会初始化A类,使得变量b在方法区内拥有特定的内存和初始值。
3.当JVM初始化一个类时,要求它的所有父类都已经初始化完毕,但是这条规则并不适用于接口。
01.在初始化一个类时,并不会先初始化它所实现的接口。
02.在初始化一个接口时,并不会先初始化它的父接口。
因此,一个父接口不会因为它的子接口或者实现类被初始化而初始化,只有当程序首次使用特定接口的静态变量时,才导致该接口的初始化。
4.只有当程序访问的静态变量或静态方法的确在当前类或接口中定义时,才可看作是对类或接口的主动使用。观察下面例子:
控制台结果:
init father
1
father method
5.调用ClassLoader类的loadClass()方法加载一个类,该方法只是加载该类,并不是对类的主动使用,不导致类的初始化。使用Class.forName()静态方法才会导致强制初始化该类。
控制台结果:
after load A
before init A
init A
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-87852-3.html