美文网首页
编译期常量与运行期常量的区别

编译期常量与运行期常量的区别

作者: 大鹏_xzlp | 来源:发表于2018-07-08 16:22 被阅读0次

    我们先来看一下下面这个程序

    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课程

    相关文章

      网友评论

          本文标题:编译期常量与运行期常量的区别

          本文链接:https://www.haomeiwen.com/subject/gyuouftx.html