Java常量池总结
一、基本概念介绍
什么是常量
用final修饰的变量即为常量(成员变量、局部变量、静态变量,只要被final给修饰过的)。
什么是常量池
二、主角运行时常量池
运行时常量池的好处:
1、节省内存,如字符串,可以无需频繁地新建内存或者销毁内存,直接在常量池共享一个字符串;
2、节省操作时间,通过==比较字符串是否相同效率显然是优于equals方法的,如果是常量池中同一个内容的引用,就无需进行equals比较了。
三、运行时常量池的作用
Java共有八种基础数据类型,都有各自的封装类,基本上都有对常量池的利用。
其中除了Float和Double,其他的六种,byte,short,int,long,boolean,char大多对常量池有利用。
在-128到127这个区间的数,会先使用常量池中的缓存内容,大于这个区间则需要新建一个对象,源码如下:
//Integer 缓存代码 :
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
再拿Integer做个例子:
Integer i1 = 40;
Integer i2 = 40;
System.out.println(i1==i2);//输出TRUE
由于40<127,所以两者都是指向了常量池中的缓存,故,输出true。
注:如果显式地去new一个Integer对象,就会直接在堆中新建一个对象了。
以下为更为丰富的一个例子:
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));
结果:
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
总结:只要使用+或者显式地写一个等号赋值,默认的,都会去常量池中找内容。
四、String类和常量池的关系
String有两种创建方式,一种是直接等号赋值,一种是new String("***")。
两者还是有本质上的区别的。
第一种是在常量池中创建对象,一种是在堆中创建对象。
使用+也可以生成String对象,如果加号中有String对象的引用而不是引号文本,则生成的String对象不会被加入常量池,如果加号中全部是引号文本,生成的对象会被加入常量池。
也有特例:
1、特例1:
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
String s = A + B; // 将两个常量用+连接对s进行初始化
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
s等于t,它们是同一个对象
由于在编译期A和B就已经固定值了,s=A+B,效果等同于s="ab"+"cd"。
2、特例2:
public static final String A; // 常量A
public static final String B; // 常量B
static {
A = "ab";
B = "cd";
}
public static void main(String[] args) {
// 将两个常量用+连接对s进行初始化
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
s不等于t,它们不是同一个对象
由于A和B的值,并不是在编译期就固定。
说说创建了几个对象的问题:
考虑类加载阶段和实际执行时。
(1)类加载对一个类只会进行一次。"xyz"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"xyz"字符串被驻留过则不需要重复创建用于驻留的"xyz"实例)。驻留的字符串是放在全局共享的字符串常量池中的。
(2)在这段代码后续被运行的时候,"xyz"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
这条语句创建了2个对象
Intern方法和更加丰富的String例子
运行期可以通过intern方法将String对象放入运行时常量池中,看以下的几个例子:
public class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.println((hello == "Hello") + " ");
System.out.println((Other.hello == hello) + " ");
System.out.println((other.Other.hello == hello) + " ");
System.out.println((hello == ("Hel"+"lo")) + " ");
System.out.println((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
package other;
public class Other { public static String hello = "Hello"; }
结果:
true true true true false true```
在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.
在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.
五、不同JDK版本下的intern方法
简言之,在JDK1.6中,当常量池中没有对象,intern方法会直接在常量池创建对象,并返回常量池的引用。
而在JDK1.7以后,intern如果在常量池中发现了字符串,还是和JDK1.6一样处理,不同的是,当常量池中没有字符串的时候,就不会再去把堆中的字符串拷贝到常量池中了,而是将堆的引用拷贝到常量池。
注:JDK1.6以前的常量池在方法区,而JDK1.6以后的常量池在堆上。
可以看这个链接:传送门