我在跟着[张龙老师]学习JVM 之 静态常量

常量

  • 在java 中凡是被 final 所修饰的变量——称为常量
  • 常量在编译阶段会存入到调用这个常量的方法所在类常量池中。
  • 本质上,调用类并没有直接引用定义常量的类,因此并不会触发定义常量类的初始化

案例一(代码):

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static String name="static TestA";
    static {
        System.out.println("my name is TestA");
    }
}

案例一(结果):

my name is TestA
static TestA

  调用 TestA 类的静态变量 会触发 TestA 的初始化,也就导致了 静态块被执行(曾经搞不懂为什么静态块只会被加载一次,现在明白,因为类只会被初始化一次)。最后执行 静态变量 name 的结果


 

  那看看 若 将 name 用 final 修饰会怎样?

案例二(代码):

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static final String name="static TestA";
    static {
        System.out.println("my name is TestA");
    }
}

案例二(结果):

static TestA

  只是打印了常量 name 的结果,并没有初始化 TestA?

解析说明:

  结合上面的话来理解就是,当调用TestA类中常量(name)的时候,在编译阶段会将 常量name 存放到调用该常量的方法(main)所在的类(Test1)的常量池中。换句话说,既然已经存放到了Test1的常量池中,调用常量name 是和TestA这个类是没有关系的。既然没有关系,就肯定不会初始化 TestA 这个类了。

   既然没有与TestA 类没有关系,即便把TestA.class文件删除掉都是没有问题的。

 

 

依然可以执行。若将 final 去掉呢?

 则会重新初始化TestA类并生成一个 TestA.class 文件

 

 反编译:

  结合 对 class文件反编译,看看 静态变量 和常量有什么不同(观察红色部分)。

静态变量(代码):

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static String  name="static TestA";
    static {
        System.out.println("my name is TestA");
    }
}

静态变量(结果):

$ javap -c  Test1.class
Compiled from "Test1.java"
public class com.test.Test1 {
  public com.test.Test1();
    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/test/TestA.name:Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: return
}

常量(代码):

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static final String  name="static TestA";
    static {
        System.out.println("my name is TestA");
    }
}

常量(结果):

$ javap -c  Test1.class
Compiled from "Test1.java"
public class com.test.Test1 {
  public com.test.Test1();
    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 static TestA
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

常量的一些注记符

  • ldc 表示将 int 、float、String类型的常量值从常量池中推送至栈顶。 所谓栈顶就是即将要要被使用到的
  • bipush 表示将单字节(-128~127)的常量推送至栈顶
  • sipush 表示将一个短整型常量指(-32768~32767)推送至栈顶
  • iconst_1 表示将 int 类型1推送至栈顶(iconst_0~inconst_5)
  • iconst_m1 表示将 int 类型 -1 推送至栈顶 
  • ldc2_w 表示将double 类型的常量推送至栈顶
  • getstatic  表示将静态变量推送至栈顶
  • 更多标记符:请看https://www.cnblogs.com/zhangzhonghui/articles/11870661.html

注记符有很多,这也是我第一次接触注记符,注记符远远不止这么一点,我也在上课的是否跟着学习了一了一点,等接触了更多知识,整理一篇博客来讲述注记符。

也接触了反编译,曾经觉得如此的高大上,接触之后才会觉得原来这样啊~

 


 

注意:当常量被定义成字面量时才不会导致 该定义常量的类被初始化,换句话说只有编译期间明确知道这个常量值,才能在编译期间讲该常量值放到调用该常量的类的常量池中。

案例(代码):注意红色部分,将name  定义成一个随机的字符串,请问 "my name is TestA" 是否会被执行?

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static final String  name= UUID.randomUUID().toString();
    static {
        System.out.println("my name is TestA");
    }
}

案例(结果): TestA 类 被初始化了,也就导致 该类的静态块被执行。

my name is TestA
a4e9c7a4-a856-4304-beaf-52fefb7a6e27

反编译的结果:是一个静态变量

$ javap -c Test1.class
Compiled from "Test1.java"
public class com.test.Test1 {
  public com.test.Test1();
    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/test/TestA.name:Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: return
}

 

 


 

空常量:将常量定义为null

   案例(代码):将 常量 name 赋值为null

public class Test1 {
    public static void main(String[] args) {
        System.out.println(TestA.name);
    }
}


class TestA  {
    public static final String  name=null;
    static {
        System.out.println("my name is TestA");
    }
}

   案例(结果):TestA 竟然被加载了

my name is TestA
null

   又把我三观弄塌了,说好和 TestA 没关系了呢(扎心)?难道编译期间不确定 这个 name 的值 为 null?

  遇到不懂的问题,就去寻找答案,还好学习反编译,和怎么看反编译的内容,于是将.class文件进行了反编译。

Compiled from "Test1.java"
public class com.test.Test1 {
  public com.test.Test1();
    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/test/TestA.name:Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: return
}

  请看红色部分,上面我也对静态变量进行了反编译,却发现反编译结果为 静态变量?也只是为什么 TestA 类被初始化了(原来调用的还是一个静态变量,所以导致TestA初始化)。

  问题已经找到了(这里说明一下,定义为空字符串是没有问题的),但是为什么会被当成 静态变量处理呢?明明对 name 进行 final 修饰。

   遇到不懂的问题,先思考,再找度娘,看了看别人阐述 null 的。https://blog.csdn.net/zebe1989/article/details/82855631

 

 

  对于这个答案,我还是有点半信半疑,但是这又是目前比较具有说服力的答案(对于我来说)。既然没有分配内存,又怎么推送到栈顶,进行输出呢?我这是想到一个问题,那么常量池又是怎么存数据的呢?面向百度编程功底还是可以的,紧接着我很快发现了一篇新博客
  讲常量结构的时候,是这样的。  https://blog.csdn.net/qq_30137611/article/details/70273291 

  其中有一个就是类型,那请问 null 属于什么类型(null 不是一个有效的实例,而是一个特殊的值)?对于这个特性,我是否可以理解 在编译期间是否无法确定 null?

  对于这个问题,如果你有什么建议或意见,下方留言。

posted @ 2019-11-14 23:21  不朽_张  阅读(450)  评论(0)    收藏  举报