9道题来理解什么是类的主动引用和被动引用

2023-02-02  本文已影响0人  曹大大

剖析类的初始化顺序?

题目:

public class Main {
    static {
        System.out.println("Main static init...");
    }
    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();
    }
}

public class Father {
    static{
        System.out.println("Initialize class father");
    }
}

public class Son extends Father {
    public static int width = 60;
    static {
        System.out.println("Initialize class son");
        width = 30;
    }
    public Son(){
        System.out.println("Son init...");
    }
}

执行结果:

Main static init...     //执行Main的静态块<clinit>方法
Main main begin...
Initialize class father //执行了Father的<clinit>方法
Initialize class son    //执行了Son的<clinit>方法
Son init...             //执行了Son的<init>方法
30
Son init...

剖析类的初始化顺序?

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

提示

<clinit>方法:

用于静态变量或者静态代码块的初始化。当类class的初始化的时候,调用该方法。

<init>方法:

用于对象实例初始化时被调用到。每次new对象的时候,都会调用该方法。

执行的顺序是<clinit>–>main–><init>(重要)。

主动引用

笔试题一:

public class Test1 {
    static {
        System.out.println("Test1 static");
    }
}

public class Main {
    public static void main(String[] args) {
        Test1 t = new Test1();
    }
}

结果:

Test1 static

主动引用定义1:

new一个类的时候会发生初始化,调用<clinit>方法

笔试题二:

public class Test2 {
    static final int count = 1;

    static {
        System.out.println("Test2 static");
    }
}

public class Main {
    public static void main(String[] args) {
        int count = Test2.count;
    }
}

执行结果:没有任何输出

主动引用定义2:

调用类中的静态成员,除了final字段,final被调用但是没有初始化类。就是因为,那个final字段,Java编译器把这样的字段解析成对常量的本地拷贝。常量是一种特殊的变量,因为编译器把他们当做值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到的那个域的代码都需要重新编译。

笔试题三:

public class Test3 {
    static void outPut(){
        System.out.println("OutPut !");
    }
    static {
        System.out.println("Test3 static");
    }
}

public class Main {
    public static void main(String[] args) {
        Test3.outPut();
    }
}

执行结果:

Test3 static  //调用静态方法先初始化类执行<clinit>方法
OutPut !

主动引用定义3:

调用某个类的静态方法,那么这个类一定先初始化。

笔试题四:

public class Test4 {

    static final int count = 1;

    static {
        System.out.println("Test4 static");
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.czy.笔试题.Test4");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

执行结果:

Test4 static

主动引用定义4:

反射调用会触发类的初始化。使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

笔试题五:

public class Father {
    static{
        System.out.println("Initialize class father");
    }
}

public class Son extends Father {
    static {
        System.out.println("Initialize class son");
    }
}

public class Main {
    public static void main(String[] args) {
        Son son = new Son();
    }
}

执行结果:

Initialize class father
Initialize class son

主动引用定义5:

先初始化父类,再初始化子类。当初始化一个类的时候,如果发现父类没有初始化,则需要先触发其父类的初始化。

笔试题六:

public class Test6 {

    static {
        System.out.println("static init...");
    }
    public static void main(String[] args) {
        System.out.println("main begin...");
    }
}

执行结果:

static init...
main begin...

主动引用定义6:

当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会首先初始化这个主类。

image-20220829161108883.png

总结

  1. new一个类的时候会发生初始化,调用<clinit>方法
  2. final被调用但是没有初始化类。就是因为,那个final字段,Java编译器把这样的字段解析成对常量的本地拷贝。
  3. 调用某个类的静态方法,那么这个类一定先初始化。
  4. 反射调用会触发类的初始化。
  5. 当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会首先初始化这个主类。

被动引用

笔试题一:

public class Father {
    static int count = 1;
    static {
        System.out.println("Initialize class father");
    }
}

public class Son extends Father {
    static {
        System.out.println("Initialize class son");
    }
}

public class Main {
    public static void main(String[] args) {
        int count = Son.count;
    }
}

结果:

Initialize class father

被动引用定义1:

通过子类引用父类的静态字段,不会导致子类初始化。虽然是以Son.count形式调用的,但是因为count是father的静态成员变量,所以只初始化father类,而不初始化Son。

笔试题二:

public class Test1 {
    static {
        System.out.println("Initialize class Test1");
    }
}

public class Main {
    public static void main(String[] args) {
        Test1[] s = new Test1[10];
    }
}

执行结果:无

被动引用定义2:

通过数组定义类引用类,不会触发类的初始化。

笔试题三:

public class Test2 {
    static final int count = 1;
    static {
        System.out.println("Initialize class Test2");
    }
}

public class Main {
    public static void main(String[] args) {
        int count = Test2.count;
    }
}

执行结果:无

被动引用定义3:

不会触发定义常量的类的初始化。常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化,Test2类中定义的count是fianl对象,其在编译阶段就会存入调用类的常量池中。

image-20220830134231892.png

总结:

  1. 通过子类引用父类的静态字段,不会导致子类初始化。
  2. 通过数组定义类引用类,不会触发类的初始化。
  3. 不会触发定义常量的类的初始化。
上一篇下一篇

猜你喜欢

热点阅读