一道有趣的类加载面试题
- 2019 年 10 月 29 日
- 筆記
题目
运行如下代码的Test1与Test2分别输出什么结果
public class Parent { static { System.out.println("Parent static invoke"); } public static final String FINAL_STR="FINAL_STR"; public static final Test FINAL_OBJECT=new Test(); public Parent(){ System.out.println("Parent init"); } } public class Child extends Parent { static { System.out.println("Child static invoke"); } public Child(){ System.out.println("child init"); } } public class Test { public static void main(String[] args) { System.out.println(Child.FINAL_STR); } } public class Test2 { public static void main(String[] args) { System.out.println(Child.FINAL_OBJECT); } }
结果:
运行Test1结果
FINAL_STR
运行Test2结果
Parent static invoke cn.lonecloud.Test@5e2de80c
解析:
Test1结果解析:
- 由于在mian方法中打印语句调用的是Child.FINAL_STR变量。
- 从Child的类中可以得知,
FINAL_STR
为final并且为static
变量,其在调用static final
变量的时候不会触发类的初始化操作。所以结果如上
Test2结果解析:
- 由于Test2中引用的对象为父类Parent的静态变量,由于并不是常量池中的对象,所以,会触发Parent的初始化操作。
深究:
Test1
字节码:
Compiled from "Test.java" public class cn.lonecloud.Test { public cn.lonecloud.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String FINAL_STR 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
ldc #4 // String FINAL_STR
其为获取静态化变量方法,其为将常量压入栈中,由于静态变量在JVM中存在常量池的概念,会将字符串进行优化,所以并不会触发类初始化
Test2字节码:
Compiled from "Test.java" public class cn.lonecloud.Test { public cn.lonecloud.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field cn/lonecloud/Child.FINAL_OBJECT:Lcn/lonecloud/Test; 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 9: return }
由于需要使用到Child类的父类中的
FINAL_OBJECT
变量,未使用到Child
类中的变量,所以不会对Child类进行初始化,初始化的为其父类。
查看JVM加载情况
通过添加JVM参数
-XX:+TraceClassLoading
可以查看类加载情况
可见,会对Child,Parent类进行类加载操作,但是调用static方法,只有Parent类会调用static进行初始化操作。
总结
- 如果引用了常量池变量(String,以及基本类型相关变量),如果该变量为
final static
进行修饰的时候,则不会对类进行初始化操作 - 如果为非常量池变量,如果调用方存在父子类关系,则实际JVM会加载子类与父类,但是如果使用的为父类的final变量,并不会触发类的初始化操作。