1 常量池是什么
.java
文件会编译为.class
文件,常量池指的是.class
文件中一部分,可以理解为.class
文件的资源仓库
[1]
。常量池中主要存放两类常量:
- 字面量 (Literal)
- 文本字符串、声明为final的常量值等
- 符号引用 (Symbolic Reference)
- 类和接口的全限定名 (Fully Qualified Name)
- 字段(field)的名称和描述符(Descriptor)
- 方法(method)的名称和描述符
在常量池中,所有类型的常量均存放在表结构中,但是不同的常量类型对应的表结构也不相同, 具体结构需要参考Java Language Specification。
在Java程序执行的过程中,上述常量池会加载到虚拟几种,成为运行时常量池(Runtime Constant Pool)。
1.1 基本数据类型的包装类对常量池的使用
Java程序中基本类型的变量可以直接在常量池中读取字面量。基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean
。另外Byte,Short,Integer,Long,Character
这5种整型的包装类只是在对应值小于等于127时才可使用常量池,见示例代码内的注释:
public class Test{
Integer i1=new Integer(1);
Integer i2=new Integer(1);
//i1,i2对象具有不同的内存地址
System.out.println(i1==i2);//输出false
Integer i3=1;
Integer i4=1;
//i3,i4指向常量池中同一个字面值,即同一个内存位置,因此下面语句返回true
System.out.println(i3==i4);//输出true
//而i1,i3位于不同的内存位置,会返回false
System.out.println(i1==i3);//输出false
}
但是如果上述代码中,int的值大于127或者小于-128,那么变量不会引用常量池中的字面量,而是创建新的变量。
Integer i1 = 1111;
Integer i2 = 1111;
//由于常量值对于127,不会使用常量池技术,因此输出false
System.out.println(i1==i2);
//但是两个变量的内容是相等的,因此输出true
System.out.println(i1.equals(i2));
2 模拟基本数据类型对常量池的使用
在编译过程中,编译器将Integer i = 5
之类的语句编译为Integer i = Integer.valueOf(5)
。实际上,Java正是通过Iteger.valueOf(int i)
方法实现了基本数据类型的自动装箱。同时,在valueOf()
方法中使用常量池技术。代码模拟如下:
public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
private static class IntegerCache {
private IntegerCache(){};
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++){
cache[i] = new Integer(i - 128);
}
}
}
上述代码中valueOf()
方法首先判断数据是否小于127且大于-128,如果符合条件,那么直接返回已经缓存好的对象,这些对象在类加载时候由static
语句块(一般用于初始化静态变量)初始化。可以看出,相同字面值的对象具有相同的内存地址。
参考文献
[1] 周志明. 深入理解Java虚拟机[M]. 机械工业出版社, 2011.