类加载的初始化阶段对类变量赋予正确的值。主要有两种初始化方式,一种是通过类变量初始化语句;一种是静态初始化语句。如下述代码所示,前者是类变量初始化语句,后者是静态初始化语句。
public class Example1 {
static int width;
static int height = (int) (Math.random() * 2.0);
static {
width = (int) (3 * Math.random() * 5.0);
}
}
所有的类变量初始化语句和静态初始化语句都被Java编译器收集在一起,放在一个特殊方法里。对于类而言,该方法称为类初始化方法,对于接口而言,该方法称为接口初始化方法。在Java class文件里,类和接口的初始化方法统一被称作为
方法。并且这种方法只能被Java虚拟机调用,Java程序是无法调用的。
初始化一个类包含两个步骤:
如果类存在超类,先初始化超类
如果类存在类初始化方法,就执行此方法
初始化一个接口只有一个步骤:
如果该接口存在接口初始化方法,就执行此方法,接口不初始化父接口。
注意:初始化类的过程必须保持同步,如果有多个线程初始化一个类,仅仅允许一个线程执行初始化,其他的线程都需要等待。
()
方法 ()
中,比如查看Example1.java的class源文件,我们可以发现()
如下: 0: invokestatic #2 // Method java/lang/Math.random:()D
3: ldc2_w #3 // double 2.0d
6: dmul
7: d2i
8: putstatic #5 // Field height:I
11: ldc2_w #6 // double 3.0d
14: invokestatic #2 // Method java/lang/Math.random:()D
17: dmul
18: ldc2_w #8 // double 5.0d
21: dmul
22: d2i
23: putstatic #10 // Field width:I
26: return
方法先执行类变量初始化语句,初始化height,接着执行了静态初始化语句,初始化了width。
并非每个类都拥有
方法,以下三种情况就没有
类没有申明类变量,也没有任何静态初始化语句;
类申明了类变量,但是没有任何的类变量初始化语句,页没有静态初始化语句进行初始化;
类近包含静态final变量的类变量初始化语句,而且是编译时候的常量;
Example2 .java
public class Example2 {
static final int angle = 35;
static final int length = angle * 2;
}
没有
方法
Example3 .java
public class Example3 {
static final int angle = (int) (35 * Math.random());
static final int length = angle * 2;
}
有
方法,因为Math.random()不是编译器常量,因此angle和length都要进行初始化
0: ldc2_w #2 // double 35.0d
3: invokestatic #4 // Method java/lang/Math.random:()D
6: dmul
7: d2i
8: putstatic #5 // Field angle:I
11: getstatic #5 // Field angle:I
14: iconst_2
15: imul
16: putstatic #6 // Field length:I
19: return
对于接口Example4.java
public interface Example4 {
int angle = (int) (35 * Math.random());
int length = 2;
}
接口中的变量默认是public static final,可以查看class文件看到
public static final int angle;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final int length;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2
而且把编译期常量2之前赋值好,不产生
方法。但是会为变量angle产生
方法,因为他的值是运行期决定的。
0: ldc2_w #1 // double 35.0d
3: invokestatic #3 // Method java/lang/Math.random:()D
6: dmul
7: d2i
8: putstatic #4 // Field angle:I
11: return
接下来我们看Java虚拟机何时初始化类变量
2. 主动使用和被动使用
那么Java虚拟机何时初始化类变量呢?主动使用的时候初始化。主动使用一共有6种情况:
1)创建某个类的新实例(new,不明确的创建,反射,克隆或反序列化);
2)调用类的静态方法(即执行字节码invokestatic指令);
3)使用某个类的或接口的静态字段,或者对该字段赋值(即执行字节码getstatic,putstatic指令),用final修饰的静态字段除外,因为被初始化为一个编译时的常量表达式;
4)调用Java API中的反射方法
5)初始化某个类的子类(某个类被初始化,超类必须已经被初始化了)
6)虚拟机启动某个被标明启动类的类(包含main方法的那个类),和条款3类似,静态方法
被动使用,子类或者子接口使用了父类或父接口的非常量静态变量;此时该子类或子接口不发生初始化。
也就是说主动使用中的字段必须是该类或该接口明确申明的,而不是父类或父接口中的。看下面这个例子:
public class NewParent {
static int hoursOfSleep = (int) (Math.random()*3.0);
static {
System.out.println("NewParent was initialized");
}
}
public class NewbornBaby extends NewParent {
static int hoursOfCrying = (int) (6 + (int)( Math.random() * 2.0));
static {
System.out.println("NewbornBaby was initialized");
}
}
测试代码1:
public class Example5 {
public static void main(String[] args) {
int hours = NewbornBaby.hoursOfSleep;
}
static {
System.out.println("Example5 was initialized");
}
}
输出结果:
Example5 was initialized
NewParent was initialized
NewbornBaby调用的字段不是本身的,是父类的,因此发生了被动引用,NewbornBaby不进行初始化,也不需要被加载。只会导致Example5和NewParent发生初始化。那么NewParent初始化是因为是NewbornBaby父类初始化还是被调用了字段而初始化呢?看下面例子
测试代码2:
public class Temp {
static int tempValue = (int) (Math.random()*3.0);
static {
System.out.println("Temp was initialized");
}
}
public class Example5 {
public static void main(String[] args) {
int hours = Temp.tempValue;
}
static {
System.out.println("Example5 was initialized");
}
}
测试结果:
Example5 was initialized
Temp was initialized
可见是因为字段被调用了而发生了初始化。
接下来看一个主动引用的例子
测试代码3:
public class Example5 {
public static void main(String[] args) {
int hours = NewbornBaby.hoursOfCrying;
}
static {
System.out.println("Example5 was initialized");
}
}
输出结果:
Example5 was initialized
NewParent was initialized
NewbornBaby was initialized
NewbornBaby调用自身的字段,因此发生了主动引用,初始化NewbornBaby,但是初始化子类之前,父类必须被初始化,因此NewParent也被初始化。
如果一个自动既是静态的又是final的,并且使用一个编译时常量表达式初始化,使用这样的字段就不是对该字段所在类的主动使用。Java编译器会把这样的字段解析成常量的本地拷贝。
测试代码4:
public class Angry {
static final String greeting = "hello world!";
static {
System.out.println("Angry is initialized");
}
}
public class Example6 {
public static void main(String[] args) {
String str = Angry.greeting;
}
static {
System.out.println("Example6 was initialized");
}
}
测试结果:
Example6 was initialized
类Angry未执行初始化过程