Java中的常量池分为两种型态:
- 静态常量池
- 运行时常量池
静态常量池
所谓静态常量池是指class文件中的常量池,存在于文件中而非内存里面,包括字面量
和符号引用量
。
-
字面量
:是Java语言层面的常量的概念,比如字符串和final修饰的常量值。 -
符号引用量
:偏属于编译原理方面的概念,比如类和接口的全限定名,字段名称和描述符,方法名称和描述符。
运行时常量池
运行时常量池是虚拟机在类加载后将class文件中的常量池载入到内存里面,是内存的一部分。
Java语言并不要求常量一定只有在编译期才能产生,也就是并非预置入class文件中的常量池的内容才能进入运行时常量池,运行期间也可以将新的常量放入常量池中,比如Sting类的intern()
方法)
运行时常量池引发的"=="比较问题
包装类和常量池
如果把常量池理解为常量的一种贡献方案,缓存到内存里面的数据也可以看做是常量池
final Integer a1 = 20;
final Integer a2 = 20;
System.out.println(a1==a2);//true
final Integer b1 = 200;
final Integer b2 = 200;
System.out.println(b1==b2);//false
final Integer c1 = new Integer(20);
final Integer c2 = new Integer(20);
System.out.println(c1==c2);//false
final Integer d1 = 30;
final int d2 = 30;
System.out.println(d1==d2);//true
final Integer e1 = 200;
final int e2 = 200;
System.out.println(e1==e2);//true
要想理解以上的结果,必须弄清楚三个概念:
- “==”运算符比较的是什么
- Java的自动装箱和拆箱
- 数值缓存
-
==运算符
对于==
运算符,如果符号两边是引用类型,那么比较的是两个引用对象的地址;如果符号两边是基本类型,那么比较的是值的大小。 -
自动装箱和拆箱
自动装箱和拆箱从Java 1.5开始引入,自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。
自动装箱时编译器调用valueOf将原始类型值转换成对象,自动拆箱时,编译器通过调用类似intValue()这类的方法将对象转换成原始类型值。 -
数值缓存
Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
对于Integer value = 20
这样的代码,Java在编译的时候会直接将代码封装成Integer value = Integer.valueOf(20)
。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
从valueOf()
方法可以知道,当创建的Integer
对象的值在[IntegerCache.low,IntegerCache.high]
之间时,会从IntegerCache
的cache
中获取值,而当创建的Integer
对象超过了[IntegerCache.low,IntegerCache.high]
这个区域,将会创建一个新的对象。
IntegerCache
的实现
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
IntegerCache
是一个内部类,会在使用的时候加载并初始化变量cache
的值。
所以对于a变量而言,由于值的范围在[-128,127]之间,所以从cache
中获取值,所以a1和a2指向同一片内存地址;对b变量而言,由于数值超出了缓存值的范围,所以会创建一个新的对象,即b1和b2是两个不同的对象;对于c变量而言,由于使用new
关键字会创建新的对象,所以c1和c2s是两个不同的对象;对于d变量而言,由于==
右边是一个基本类型,所以==
左边的包装类型会自动拆箱,==
比较的是两个基本类型的大小;对于e变量原理等同于d变量。
字符串类和常量池
由于编译期间,编译器会把字符串常量编译到class文件的常量池中,在解析阶段虚拟机会把class文件中的常量池加载到内存的运行时常量池里面,所以字符串的创建一般有两种方式:
- 1.是直接从常量池里拿对象的引用
- 2.在堆里新建一个对象并返回对象的引用
一个比较经典的问题String s1 = new String("xyz")
创建了几个对象?
- 改行代码在编译期间,编译器发现有一个字符串常量
“xyz”
,于是便把这个常量放到class文件的常量池里面,在虚拟机解析阶段,虚拟机会创建一个常量放入堆中,并将对象的引用放到运行时常量池,这是第一个阶段,该阶段创建了一个对象 - 当程序运行到改行代码的时候,遇见
new
关键字,会创建一个对象。
所以综上,可以发现该行代码生成了两个对象,一个是在加载类的时候解析阶段,一个是在程序运行的阶段。
String类的intern()方法
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。
String类对运算符的重载
唯一被重载的运算符就是字符串的拼接相关的
+
、+=
,除此之外,Java设计者不允许重载其他的运算符。
只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。
String s1 = "wang";
String s2 = "wang";
System.out.println(s1==s2);//true
String t1 = "shanghai";
String t2 = "shang"+"hai";
System.out.println(t1==t2);//true
String f1 = "shanghai";
String f2 = "shanghai"+new String();
System.out.println(f1==f2);//false
String c1 = "bei";
String c2 = "jing";
String c3 = c1 + c2;
String c4 = "beijing";
System.out.println(c3==c4);//false
final String o1 = "chong";
final String o2 = "qing";
String o3 = o1+o2;
String o4 = "chongqing";
System.out.println(o3==o4);//true
网友评论