Appearance
本篇文章将从一个笔试题分析类的初始化顺序,再通过9个案例讲解什么是类的主动引用和被动引用。
笔试题:分析这段代码输出结果是什么?
java
public class Main {
static {
System.out.println("Main static");
}
public static void main(String[] args) {
System.out.println("Main main begin");
Son a = new Son();
System.out.println(Son.width);
Son b = new Son();
}
}
class Father {
static {
System.out.println("Father static");
}
}
class Son extends Father{
public static int width = 60;
static {
System.out.println("Son static");
width = 30;
}
public Son() {
System.out.println("Son Init");
}
}
执行顺序如下:
java
Main static // 执行Main的<clinit>方法
Main main begin
Father static // 执行Father的<clinit>方法
Son static // 执行Son的<clinit>方法
Son Init // 执行Son的<init>方法
30
Son Init // 执行Son的<init>方法
TIP
剖析类的初始化顺序
- 先执行父类的
<clinit>
方法,再执行子类的<clinit>
方法 - 执行父类的
<init>
方法,再执行子类的<init>方
法 <clinit>
方法只执行一次(class对象只执行一次),<init>
方法可以执行多次(因为可以多次new对象)
类的主动引用
将用几个实验来讲述什么是类的主动引用,总结如下:
- new一个类的时候会发生类初始化,调用
<clinit>
方法。 - 如果仅调用final字段不会触发类的初始化。
- 调用某个类的静态方法,那这个类一定先初始化,调用
<clinit>
方法。 - 反射调用,触发类初始化。
- 先初始化父类,再初始化子类。
- 当虚拟机启动时,用户需要指定一个执行的主类,虛拟机会首先初始化这个主类。
实验一
java
public class Main {
public static void main(String[] args) {
Test1 t = new Test1();
}
}
class Test1 {
static {
System.out.println("Test1 static");
}
}
执行结果:
java
Test1 static
主动引用定义1:new一个类的时候会发生类初始化,调用<clinit>
方法。
实验二
java
public class Main2 {
public static void main(String[] args) {
int n = Test2.count;
}
}
class Test2{
static final int count = 1;
static {
System.out.println("Test static");
}
}
上述代码的执行结果是空,这是为什么?
主动引用定义2:调用类中的静态成员,除了final字段,final被调用但是没有初始化类,没有任何输出,就是因为那个final字段,Java编译器把这样的字段解析成对常量的本地拷贝。
常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
实验三
java
public class Main2 {
public static void main(String[] args) {
Test.Output();
}
}
class Test {
static final int count = 1;
static void Output(){
System.out.println("Output");
}
static {
System.out.println("Test static");
}
}
输出结果如下:
java
Test static
Output
TIP
主动引用定义3:调用某个类的静态方法,那这个类一定先初始化。 调用<clinit>
方法.
实验四
java
public class Main2 {
public static void main(String[] args) {
try {
Class aClass = Class.forName("com.xk857.test.Test");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Test {
static final int count = 1;
static {
System.out.println("Test static");
}
}
结果如下:
java
Test static
主动引用定义4:反射调用,触发类初始化 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。通过调用java.lang.Class.forName(String className)
实验五
java
public class Main2 {
public static void main(String[] args) {
Son son = new Son();
}
}
class Son extends Father {
static {
System.out.println("Init class son");
}
}
class Father {
static {
System.out.println("Init class Father");
}
}
结果
java
Init class Father
Init class son
主动引用定义5:先初始化父类,再初始化子类。
实验六
java
public class Test6 {
static {
System.out.println("static test6");
}
public static void main(String[] args) {
System.out.println("main begin");
}
}
结果
java
static test6
main begin
主动引用定义6:当虚拟机启动时,用户需要指定一个执行的主类,虛拟机会首先初始化这个主类。
类的被动引用
什么是被动引用?除了主动引用外,所有引用类的方式都不会触发初始化,称为被动引用。接下来将通过3各实验讲解什么是被动引用,得出结论如下:
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义引用类不会触发类的初始化
- 不会触发定义常量的类的初始化
实验一
java
public class Main {
public static void main(String[] args) {
int n = Son.count;
}
}
class Father {
static int count = 1;
static {
System.out.println("Father init");
}
}
class Son extends Father {
static {
System.out.println("Son init");
}
}
结果
java
Father init
被动引用定义1:通过子类引用父类的静态字段,不会导致子类初始化。虽然是以Son.count形式调用的,但是因为count是Father的静态成员变量,所以只初始化Father类, 而不初始化Son类。
实验二
java
public class Main {
public static void main(String[] args) {
Test[] e = new Test[10];
}
}
class Test {
static {
System.out.println("init test");
}
}
结果什么也没输出,通过数组定义引用类不会触发类的初始化。
实验三
java
public class Main {
public static void main(String[] args) {
int n = Test.count;
}
}
class Test {
static final int count = 1;
static {
System.out.println("init test");
}
}
结果什么也没输出,因为count是常量和主动引用那里相同。常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。