一道有趣的类加载面试题
题目
运行如下代码的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变量,并不会触发类的初始化操作。