前言
基本类型直接存放在栈中,所以只有包装类常量池,
Java在jdk1.5后包装类常量池使用缓存实现,本文使用1.8演示
由于字符串常量池有些特殊,我门先来理解包装类常量池,也有助于接下来理解字符串常量池
包装类常量池
基本概念
- Integer 、Long、Short、Byte、Character、Double、Float、Boolean八种基本类型中Double、Float没有常量池,Boolean只有true和value,其他5种基本相同。
- java使用缓存维护了一个 [-128,127] 的常量,正好是255个数,1个字节byte所能表示最大值
以Integer为例,源码分析 使用数组存放,最小值限定为-128,最大值可配置,但不能小于127,否则抛出错误。
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;
// 通过jvm获取缓存最大值
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() {}
}
Integer 常量池
上代码
Integer a = 128;
Integer b = 128;
Integer a2 = 127;
Integer b2 = Integer.valueOf(127);
System.out.println("1> a == b:" +(a == b));
System.out.println("2> a2 == b2:" + (a2 == b2));
Integer c = new Integer(128);
System.out.println("3> b=c:" + (b == c));
System.out.println("4> b=c.Intner:" + (b == Integer.valueOf(128)));
Integer d = 127;
Integer e = new Integer(127);
System.out.println("5> d==e:" + (d == e));
System.out.println("6> d = Integer.valueOf" + (d == Integer.valueOf(127)));
System.out.println("7> e = Integer.valueOf" + (e == Integer.valueOf(127)));
System.out.println("8> e.equals(d):" + e.equals(d));
// 执行结果
1> a == b:false
2> a2 == b2:true
3> b=c:false
4> b=c.Intner:false
5> d==e:false
6> d = Integer.valueOf true
7> e = Integer.valueOf false
8> e.equals(d):true
分析
1>使用=号赋值,导致强制拆包装,查看字节码文件,可知调用了Integer.valueOf方法。
调用了Integer.valueOf方法,下面是该方法方法体,可知,如果缓存里有该值,则返回缓存里对象,没有则创建一个新的对象。因为Integer缓存存放[-128,127]之间的数,所以1>为false。
2> a2=127自动拆装箱等于调用Integer.valueOf()方法,所以等于b2直接使用Integer.valueOf()方法
3> c是直接new对象,存放的是新的内存地址,b=128不在缓存范围中,因此也是直接new Integer,两个对象在内存中的地址不一样,所以false。
4> 与3>情况类似,哈哈,🐶
5> d=127使用自动拆装箱,会到缓存中获取,e为new对象,所以不相同
6> 因为自动拆装箱其实本质也是调用Integer.valueOf()方法,所以,为true
7> e为new对象,Integer.valueOf(127)是获取缓存中的对象,因此false
8> 在阿里java泰山版中,有这样一段话
说明中已经说明了原因,我就不再赘述,我们一起来看一下equals方法
实际就是value和intValue进行比较,我们来看一下intValue()和value
其实就是Integer对象里维护的final常量int值,该int值在对象被加载时,存放在方法区的元空间中(jdk1.8叫法),所以,equals比较的就是这两个int值,不论是直接复制,new对象,调用Integer.valueOf方法,只要是同一个数字,就都相等,所以为true.
字符串常量池
基本概念
- 为什么要设计字符串常量池?因为字符串的创建要耗费大量的时间和空间,频繁创建字符串影响程序性能。
- 创建(无论是直接赋值,还是new对象)字符串时,首先会判断字符串常量池是否有该字符串,如果有会直接引用,如果没有才会实例化该字符串,并放入常量池
上代码
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
String str5 = new String("def");
System.out.println("1> str1 == str2:" + (str1 == str2));
System.out.println("2> str3 == str4:" + (str3 == str4));
System.out.println("3> str1 == str3:" + (str1 == str3));
System.out.println("4> str1.equals(str3):" + str1.equals(str3));
System.out.println("5> str1 == str3.intern():" + (str1 == str3.intern()));
compareString(str1);
}
private static void compareString(String str0) {
String str = "abc";
System.out.println("6> str == str0:" + (str == str0));
}
// 执行结果
1> str1 == str2:true
2> str3 == str4:false
3> str1 == str3:false
4> str1.equals(str3):true
5> str1 == str3.intern():true
6> str == str0:true
分析
1> str1和str2都是压栈了String abc,所以是相等的
2>str3和str4都是创建了新对象,所以是不相等的,但是其 本质还是都引用了常量池中的abc,5>就是证明。
3>str1是直接引用常量池,str3是先指向String对象再指向常量池,是间接引用,所以false
4>equals就是用来解决直接指向和间接指向的,由源码可知其内部通过char进行逐个比较,全部相等则相等。
5>str1=str3.intern(),为啥就相等了呢?intern方法返回的是什么呢?
所以,使用intern方法,会获取该字符串的引用。
这时,你可能有另外一个疑问,那会不会是在调用intern方法时才从字符串常量池中获取引用,在new String()时,new的对象指向了另外一个字符串?
别着急,我来来看第6行打印。
6> 在调用compareString()时传入了一个字符串常量池引用,在方法体中又创建了一个字符串常量池的引用,那肯定是true啊!有这个想法就对了,我们来看一下new String()时发生了什么!
new String()也是传入了一个字符串常量。
其内部实现就是将字符串常量value char数组引用赋值了new对象中的value,所以new对象本质也是指向了字符串常量的引用地址。