小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本篇文章将介绍基本数据类型的包装类和常量池。
先来看一段程序吧:
public static void main(String[] args) {
Integer num1 = 1;
Integer num2 = 1;
Integer num3 = 200;
Integer num4 = 200;
System.out.println(num1 == num2);
System.out.println(num3 == num4);
}
复制代码
猜猜结果是什么?输出结果为:
true
false
复制代码
num1 == num2
很好理解,那为什么num3不等于num4呢?首先需要清楚的是,对于引用类型,==比较的是引用地址,所以它的值为多少其实并不影响判断,我们反编译源代码分析一下:
可以看到,Integer num1 = 1
其实对应着三条指令,分别是:
- iconst_1
- invokestatic # 2
- astore_1
最关键的是中间这条指令,它表示调用了一个静态方法,该方法是Integer类中的valueOf。
我们再通过源码来验证一下,使用Debug进行调试发现,程序第一次调用的方法就是Integer的valueOf方法:
代码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
复制代码
该方法首先会判断传入的i是否在一个区间内,这个i就是我们定义的1,这个区间就是Integer中的一个常量池:
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() {}
}
复制代码
这个常量池的范围是-128~127,它提前创建好了处在该范围之内的Integer对象,此时1肯定满足该范围,所以会从cache中获取值为1的Integer对象:
return IntegerCache.cache[i + (-IntegerCache.low)];
复制代码
cache是一个Integer类型的数组,从下标0开始到下标255,依次存储了-128到127的数,所以使用当前数字i减去IntegerCache.low(128)即可得到当前数字在数组中的下标。
因为数字1是直接从常量池中获取的,所以num1和num2实际上是同一个对象,而200不在常量池中,每次获取就会创建一个新的对象,所以num3不等于num4。
相应的,Long类型也有它的常量池:
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
复制代码
它的常量池范围也为-128127。127范围的常量池。
事实上,Byte、Short、Integer、Long四种类型均有-128
而Float和Double类型是没有的:
public static Float valueOf(float f) {
return new Float(f);
}
复制代码
public static Double valueOf(double d) {
return new Double(d);
}
复制代码
它将直接返回一个新的对象。
Boolean类型因为只有两种值,所以采用static进行修饰:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
复制代码
所以不管创建多少个Boolean类型的对象,其实都是同一个(前提是值相同)。
最后是String类型,它比较特殊,那么就先来看一段程序:
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str1 == str2);
System.out.println(str3 == str4);
}
复制代码
输出结果为:
true
false
复制代码
这是因为String类型也有其对应的常量池,当使用""
声明字符串时,JVM首先会去常量池中查找是否有相应的字符串,若有,则直接返回其引用,若没有,则将字符串放入常量池并返回引用,所以str1和str2实际上是常量池中的同一个引用。
而str3和str4也会去常量池中判断是否有对应的字符串,发现常量池中已经有hello字符串了,所以得到其引用,但因为是通过new关键字创建的字符串,所以JVM会在堆内存中开辟一个空间,并让该空间存入常量池中的引用,而我们比较的确是str3和str4,它们分别是堆内存中的两块不同区域,虽然它们的值是一样的(两块内存区域中保存的均是hello在常量池中的引用)。
近期评论