9道题来理解什么是类的主动引用和被动引用
剖析类的初始化顺序?
题目:
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...
剖析类的初始化顺序?
- 先执行父类的<clinit>方法,再执行子类的<clinit>方法
- 先执行父类的<init>方法,再去执行子类的<init>方法
- <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:
先初始化父类,再初始化子类。当初始化一个类的时候,如果发现父类没有初始化,则需要先触发其父类的初始化。
笔试题六:
image-20220829161108883.pngpublic class Test6 { static { System.out.println("static init..."); } public static void main(String[] args) { System.out.println("main begin..."); } }
执行结果:
static init... main begin...
主动引用定义6:
当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会首先初始化这个主类。
总结
- new一个类的时候会发生初始化,调用<clinit>方法
- final被调用但是没有初始化类。就是因为,那个final字段,Java编译器把这样的字段解析成对常量的本地拷贝。
- 调用某个类的静态方法,那么这个类一定先初始化。
- 反射调用会触发类的初始化。
- 当虚拟机启动时,用户需要指定一个执行的主类,虚拟机会首先初始化这个主类。
被动引用
笔试题一:
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:
通过数组定义类引用类,不会触发类的初始化。
笔试题三:
image-20220830134231892.pngpublic 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对象,其在编译阶段就会存入调用类的常量池中。
总结:
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义类引用类,不会触发类的初始化。
- 不会触发定义常量的类的初始化。