基本数据类型的包装类和常量池

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本篇文章将介绍基本数据类型的包装类和常量池。

先来看一段程序吧:

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呢?首先需要清楚的是,对于引用类型,==比较的是引用地址,所以它的值为多少其实并不影响判断,我们反编译源代码分析一下:

image.png

可以看到,Integer num1 = 1其实对应着三条指令,分别是:

  • iconst_1
  • invokestatic # 2
  • astore_1

最关键的是中间这条指令,它表示调用了一个静态方法,该方法是Integer类中的valueOf。

我们再通过源码来验证一下,使用Debug进行调试发现,程序第一次调用的方法就是Integer的valueOf方法:

image.png

代码如下:

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。
事实上,Byte、Short、Integer、Long四种类型均有-128
127范围的常量池。

而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在常量池中的引用)。