Skip to content

本篇文章将从一个笔试题分析类的初始化顺序,再通过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

剖析类的初始化顺序

  1. 先执行父类的<clinit>方法,再执行子类的<clinit>方法
  2. 执行父类的<init>方法,再执行子类的<init>方
  3. <clinit>方法只执行一次(class对象只执行一次),<init>方法可以执行多次(因为可以多次new对象)

类的主动引用

将用几个实验来讲述什么是类的主动引用,总结如下:

  1. new一个类的时候会发生类初始化,调用<clinit>方法。
  2. 如果仅调用final字段不会触发类的初始化。
  3. 调用某个类的静态方法,那这个类一定先初始化,调用<clinit>方法。
  4. 反射调用,触发类初始化。
  5. 先初始化父类,再初始化子类。
  6. 当虚拟机启动时,用户需要指定一个执行的主类,虛拟机会首先初始化这个主类。

实验一

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各实验讲解什么是被动引用,得出结论如下:

  1. 通过子类引用父类的静态字段,不会导致子类初始化。
  2. 通过数组定义引用类不会触发类的初始化
  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是常量和主动引用那里相同。常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。