Java中的常量池
Java常量池实际上分为两种形态:静态常量池和运行时常量池
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
静态常量池:
静态常量池是Class文件常量池(编译后的class文件的常量池),存放各个字面量值,符号引用的数据。
主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。
字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等。
运行时常量池(具备动态性):
是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存的方法区中。
我们常说的常量池,就是指方法区中的运行时常量池。
字符串示例
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;
System.out.println(s1 == s2); // true
//所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好
System.out.println(s1 == s3); // true
//对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
System.out.println(s1 == s4); // false
//s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定
//所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同
System.out.println(s1 == s9); // false
//二者都在堆中,但地址不同
System.out.println(s4 == s5); // false
//s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址
//因为常量池中已经有了Hello字符串,所以intern方法直接返回地址
System.out.println(s1 == s6); // true
特例一
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
String s = A + B; // 将两个常量用+连接对s进行初始化
String t = "abcd"; //s == t 成立
A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。
也就是说:String s=A+B; 等同于:String s="ab"+"cd";
特例二
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);
}
编译期间,就已经确定了c,放在字符串常量池(堆中),但编译期static不执行的,a和b的值是未知的,
static代码块,在初始化的时候被执行,初始化属于类加载的一部分,属于运行时常量池(方法区中)。
运行时是这样的String s6=new StringBuilder().append(s3).append(s4).toString();这里的过程是通过StringBuilder这个类实现的
它是通过new String()的方式来作为值进行返回的,所以是在堆中开辟的一块空间。所以和常量池中的不一样
1,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用String.equals()
2,程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。
整型常量池、浮点型常量池等
Byte,Short,Integer,Long,Character,Boolean都实现了常量池技术
数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,
比如整型常量池中的常量范围:-128~127,
Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,
但是超出此范围仍然会去创建新的对象
-128到127之 间的Integer会缓存到一个Integer数组中去了:
如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你,不必再新new一个,
如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象
Integer源码,里面有个静态类IntegerCache
它对Integer进行了缓存,范围是[-128,127],只要是这个范围内的数字都会缓存到这个里面,做成常量池进行管理
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for (int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {
}
}
字符串常量池(String Constant Pool)
存储编译期类中产生的字符串类型数据
JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中
JDK7.0版本,字符串常量池被移到了堆中了
运行时常量池和字符串常量池是独立的
String.intern()
检查字符串常量池中是否存在String并返回池里的字符串引用
若池中不存在,则将其加入池中,并返回其引用。
这样做主要是为了避免在堆中不断地创建新的字符串对象
class常量池(Class Constant Pool)
每一个Java类被编译后,就会形成一份class文件
class文件中除了包含类的版本、字段、方法、接口等描述信息外
还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)
运行时常量池(Runtime Constant Pool)
运行时常量池包含了类的运行时常量和静态方法等Class常量池的数据
JVM中运行时常量池在方法区中,是class常量池被加载到内存之后的版本
当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中
运行时常量池和字符串常量池是独立的
new关键字
使用new关键字当然是每次都是新建一个,分配自己的空间