我们先来看一下下面这个程序
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.i);
}
}
class MyParent2 {
public static final int i = 3;
static{
System.out.println("Myparent2 static block");
}
}
运行程序,输出:
3
很明显访问MyParent2的常量,并不会导致MyParent2的初始化,我们去看一下MyTest2的class文件
运行javap -c com.shengsiyuan.jvm.classloader.MyTest2
命令查看详情
public class com.shengsiyuan.jvm.classloader.MyTest2 {
public com.shengsiyuan.jvm.classloader.MyTest2();
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: iconst_3
4: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
7: return
}
这里面有很多助记符,我们看到其中一个是iconst_3
,对应的就是我们的System.out.println(MyParent2.i);
这一句输出的3,iconst_3
表示将int类型推送至栈顶,这里面并未看到MyParent2的身影,然后我们将MyParent2中的i修改成变量,再运行,查看class文件,比较下区别
Compiled from "MyTest2.java"
public class com.shengsiyuan.jvm.classloader.MyTest2 {
public com.shengsiyuan.jvm.classloader.MyTest2();
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 com/shengsiyuan/jvm/classloader/MyParent2.i:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: return
}
可以看到输出位置变成了
3: getstatic #3 // Field com/shengsiyuan/jvm/classloader/MyParent2.i:I
这里我们就可以发现以下结论:
- 常量在编译阶段会存到调用这个常量的方法所在的类的常量池中
- 本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
注意:这里指的是将常量存放到MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了,甚至我们可以将MyParent2的class文件删除
运行期常量
我们再来看一下下面这个程序
public class MyTest3 {
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("Myparent3 static code");
}
}
这个程序也是对常量的调用,运行程序,输出:
Myparent3 static code
72a9293a-5c77-468d-8aa7-a6dd0e02a429
可以看到MyParent3被初始化了,所以和编译期常量不同
- 当一个常量的值并非编译期间可以确定的,那么其值就不会被放到调用类的常量池中,
- 这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化。
常用助记符
ldc:表示将int,float或是String类型的常量值从常量池中推送至栈顶
bipush:表示将单字节(-128 ~ 127)的常量值推送至栈顶 (比如short类型)
sipush:表示将短zheng'xing整形常量值(-32768 ~ 32767)推送至栈顶
iconst_1:表示将int类型推送至栈顶,(iconst_m1 ~ iconst_5)
参考资料:
圣思园JVM课程
网友评论