【Java进阶笔记】4种常量池



1. Class文件常量池

class文件是一组二进制字节流,class文件常量池在编译阶段就已经确定。class文件常量池主要存放两大常量:字面量符号引用

class JavaBean{
    private int value = 1;
    public String s = "abc";
    public final static int f = 0x101;

    public void setValue(int v){
        final int temp = 3;
        this.value = temp + v;
    }

    public int getValue(){
        return value;
    }
}

通过 javac 命令编译之后,用 javap -v 命令查看编译后的文件:

class JavaBasicKnowledge.JavaBean
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#30         // JavaBasicKnowledge/JavaBean.value:I
   #3 = String             #31            // abc
   #4 = Fieldref           #5.#32         // JavaBasicKnowledge/JavaBean.s:Ljava/lang/String;
   #5 = Class              #33            // JavaBasicKnowledge/JavaBean
   #6 = Class              #34            // java/lang/Object
   #7 = Utf8               value
   #8 = Utf8               I
   #9 = Utf8               s
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               f
  #12 = Utf8               ConstantValue
  #13 = Integer            257
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               LJavaBasicKnowledge/JavaBean;
  #21 = Utf8               setValue
  #22 = Utf8               (I)V
  #23 = Utf8               v
  #24 = Utf8               temp
  #25 = Utf8               getValue
  #26 = Utf8               ()I
  #27 = Utf8               SourceFile
  #28 = Utf8               StringConstantPool.java
  #29 = NameAndType        #14:#15        // "<init>":()V
  #30 = NameAndType        #7:#8          // value:I
  #31 = Utf8               abc
  #32 = NameAndType        #9:#10         // s:Ljava/lang/String;
  #33 = Utf8               JavaBasicKnowledge/JavaBean
  #34 = Utf8               java/lang/Object

1.1. 字面量

字面量接近 java 语言层面的常量概念。

【字符串】

字符串的值。

// public String s = "abc"; 中的 "abc"。
   #9 = Utf8               s
   #3 = String             #31
  #31 = Utf8               abc

【final 修饰变量】

包括成员变量、静态变量、实例变量和局部变量。

// public final static int f = 0x101; 中的0x101。
  #11 = Utf8               f
  #12 = Utf8               ConstantValue
  #13 = Integer            257                   

【注意】

  • 字面量指的是数据的值,class文件常量池中一般指字符串的值和常量的值。
  • 对于基本类型数据和局部变量,他们的字面量不会存在于class文件常量池。
// private int value = 1; 常量池中只保留了字段描述符 I 和字段名称 value
   #7 = Utf8               value
   #8 = Utf8               I

1.2. 符号引用

符号引用主要设涉及编译原理方面的概念。

【类和接口的全限定名】

将类名中原来的 “.” 替换为 “/” 得到的,主要用于在运行时解析得到类的直接引用。

   #5 = Class              #33            
  #33 = Utf8               JavaBasicKnowledge/JavaBean

【字段名称和描述符】

类或者接口中声明的变量名称和类型,包括类变量和实例变量。

   #4 = Fieldref           #5.#32
   #5 = Class              #33
  #32 = NameAndType        #7:#8
// 基本数据类型的变量,常量池只保留字段名称和字段描述符
// private int value = 1; 
   #7 = Utf8               value  // 变量名
   #8 = Utf8               I      // 变量类型
// 形参变量和局部变量,常量池只保留字段名称 
// public void setValue(int v) {}
  #23 = Utf8               v      // 形参变量名
// final int temp = 3;
  #24 = Utf8               temp   // 局部变量名

【方法名称和描述符】

方法的名称和参数类型、返回值类型

// public void setValue(int v) {}
  #21 = Utf8               setValue // 方法名
  #22 = Utf8               (I)V     // 参数类型 I 返回值类型 V

  #25 = Utf8               getValue // 方法名
  #26 = Utf8               ()I      // 参数类型空 返回值类型 I

2. 运行时常量池

运行时常量池是方法区的一部分,不同的类共用一个运行时常量池。

  • Class 加载过程,class 文件常量池会进入运行时常量池。
  • 多个 class 文件常量池中相同的字符串在运行时常量池只会存在一份。
  • class 文件常量池中的符号引用,会在 Class 的解析阶段翻译成直接引用,一起存储在运行时常量池中。
  • 运行时常量池具有动态性,其中的内容并不全部来自 class 常量池,在运行时可以通过代码生成常量并将其放入,这种特性被用的最多的就是 String.intern()

3. 字符串常量池

字符串常量池和运行时常量池不是一个概念。

  • JDK 1.7 以前字符串常量池和运行时常量池都在方法区中。
  • JDK 1.7 及以后运行时常量池在方法区中,字符串常量池在堆中。

3.1. 创建字符串对象的方式

3.1.1. 字面量创建

String s = "abc";
  1. JVM 先去字符串常量池中查找 equals("abc") 的对象。
  2. 如果有则不用创建,直接将字符串常量池中的 "abc" 对象的引用赋值给 s
  3. 如果没有则在堆中创建一个新的 "abc" 对象,并将堆中的 "abc" 对象的引用驻留一份在字符串常量池中,再将字符串常量池中的 "abc" 对象的引用赋值给 s

s => 字符串常量池中(同时也是堆中)的 “abc” 对象。且无论创建多少次,只要字符串的值相同,它们都指向堆中的同一个对象。

3.1.2. 构造方法创建

String s = new String("abc");
  1. JVM 先去字符串常量池中查找 equals("abc") 的对象。
  2. 如果有则在堆中创建一个新的 "abc" 对象。
  3. 如果没有则在堆和字符串常量池中各创建一个新的 "abc" 对象。
  4. 将字符串常量池中的 "abc" 对象的引用赋值给 s

s => 堆中的 “abc” 对象。

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = new String("Hello");
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;

// true。 s1和s2都指向字符串常量池中的同一个"Hello"对象
System.out.println(s1 == s2);
// true。 s3会在编译时合并成"Hello"对象,因此s1和s3都指向字符串常量池中的同一个"Hello"对象
System.out.println(s1 == s3);
// false。s4只会创建"Hel"对象,"lo"对象会在堆中创建,因此s1和s4指向的对象不同
System.out.println(s1 == s4);
// false。s1指向字符串常量池中的"Hello"对象,s5指向堆中的"Hello"对象
System.out.println(s1 == s5);
// true。 s1指向字符串常量池中的"Hello"对象,s5.intern()会返回字符串常量池中的"Hello"对象
System.out.println(s1 == s5.intern());
// false。s9会在运行时动态赋值,因此s1和s9指向的对象不同
System.out.println(s1 == s9);
// false。s5和s6指向堆中不同的"Hello"对象
System.out.println(s5 == s6);
// true。 s5.intern()和s6.intern()都会返回字符串常量池中的同一个"Hello"对象
System.out.println(s5.intern() == s6.intern());

3.2. intern 方法

将对应的字面量进行特殊处理。

  • JDK 1.6及以前:检查字面量对象是否在字符串常量池中,如果存在则直接返回字符串常量池的对象,如果不存在,则在字符串常量池创建并返回该字面量对象。
  • JDK 1.7及以后:检查字面量对象是否在字符串常量池中,如果存在则直接返回字符串常量池的对象,如果不存在,则把堆中的字面量对象的引用添加到字符串常量池中,并返回该字面量对象。

4. 包装类对象常量池

java 基本类型的包装类 Byte,Short,Integer,Long,Character,Boolean 实现了常量池技术,而 Float 和 Double 则没有实现。

另外上面这 6 种整型的包装类也只是在对应值在 [-128,127] 区间时才使用缓存数据, 超出此范围仍然会去创建新的对象。

Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);       // true。 s1和s2都指向常量池中的同一个对象
i2 = 200;
System.out.println(i1 == i2);       // false。s2重新创建了一个新的对象,s1和s2指向不同对象
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);       // false。128超出范围,s1和s2都指向不同对象
Integer i5 = -128;
Integer i6 = -128;
System.out.println(i5 == i6);       // true。 s1和s2都指向常量池中的同一个对象
Boolean bool1 = false;
Boolean bool2 = false;
System.out.println(bool1 == bool2); // true。 s1和s2都指向常量池中的同一个对象
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1 == d2);       // false。Double类没有使用常量池。s1和s2指向不同对象
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值