对常量池理解的补充说明
常量池分类
常量池大体可以分为:静态常量池、运行时常量池
- 静态常量池:存在*.class文件中,class文件中的常量池不仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。
- 字面量:Java语言层面常量的概念,由字母,数字构成的字符串或数值,只能作为右值出现,例如文本字符串、声明为final的常量值等;
- 符号引用量:1.类和接口的全限定名 2. 字段名称和描述符 3. 方法名称和描述符
- 运行时常量池:jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中
通常说的常量池指的是运行时常量池,所以讨论也是运行时常量池
字符串常量池
String a = "abc";
String b = new String("abc");
System.out.println(a==b); // false
a作为字面量一开始存储在class文件中,之后运行期,转存方法区中;b 对象存储在堆中。
实例
String s1 = "hello";
String s2 = "hello";
String s3 = "hel" + "lo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
String s10 = new String("hello");
System.out.println(s1==s2); // true
System.out.println(s1==s3); // true
System.out.println(s1==s4); // false
System.out.println(s1==s6); // true
System.out.println(s1==s9); // false
System.out.println(s4==s5); // false
System.out.println(s10==s5); // false
分析:
- s1 == s2 都指向了方法区常量池中的 “hello”
- s1 == s3 做+运算的时候,会进行优化,自动生成“hello”赋值给s3,因此也是true
- s1 == s4 s4分别用了常量池中的字符串和存放对象的堆中的字符串,做+运算的时候会进行动态调用,最后生成的仍是一个String对象存放在堆中
- s1 == s6 intern()方法首先在常量池中查找是否存在一份equal相等的字符串,如果有的话返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class常量池并非固定不变,可以用intern()方法加入新的
- s1 == s9 在Java9中,使用的是动态调用,返回的是一个新的String对象。s4、s5、s9、s10均不是指向同一块内存
需要注意的特例
- 常量拼接
final 类型的常量存在静态常量池中,做+运算的时候,会进行优化,自动生成“123456”赋值给dpublic static final String a = "123"; public static final String b = "456"; public static void main(String[]args){ String c = "123456"; String d = a + b; System.out.println(c==d); // true }
2.static 静态代码块
public static final String a;
public static final String b;
static{
a = "123";
b = "456";
}
public static void main(String[]args){
String c = "123456";
String d = a+b;
System.out.println(c==d); // false
}
上个例子在编译期间,就已经确定a和b,但是在这段代码中,编译器static不执行,a和b的值未知,static代码块,在初始化时被执行,初始化属于类加载的一部分,属于运行期,动态返回String类型对象,存在堆中,c在方法区常量池中
包装类的常量池技术(缓存)
Java基本类型的包装类的大部分都实现了常量池技术,即Byte、Short、Integer、Long、Character、Boolean;前面4钟包装类默认创建了数值[-128,127]的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True or False.如果超出对应范围仍然会去创建新的对象;两种浮点数类型的包装类Float、Double没有实现常量池技术.
自动装箱:valueOf()
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
例子:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
Integer e = new Integer(127);
Integer f = new Integer(127);
System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(e == f); // false
System.out.println(a == e); // false
Double m = 1.0;
Double n = 1.0;
System.out.println(m == n); // false
Integer a = 127;Java 在编译的时候会直接将代码封装成 Integer a = Integer.valueOf(127),从而使用常量池中的对象。Integer e = new Integer(127);这种情况下会创建新的对象
自动拆箱:intValue()
Integer a = new Integer(50);
Integer b = new Integer(40);
Integer c = new Integer(10);
System.out.println(a == b + c); // true
因为 + 这个操作符不适用于 Integer 对象,首先 b 和 c 进行自动拆箱操作,进行数值相加,即 a == 50,然后 Integer 对象无法与数值进行直接比较,所以 a 自动拆箱转为 int 值 50,最终这条语句转为 50 == 50 进行数值比较。
参考:
I'm so cute. Please give me money.
- 本文链接:https://wentianhao.github.io/2021/07/22/java%E5%B8%B8%E9%87%8F%E6%B1%A0/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。