Integer比较问题和JDK缓存设计

Integer比较问题

今天旁边搞.net的同事问我,为什么Java中Integer比较得到的结果这么奇怪?

class Test {
    public static void main(String[] args) {
        Integer a = 1024;
        Integer b = 1024;
        //比较1
        System.out.println(a == b);//false
        //比较2
        System.out.println(a == 1024);//true
    }
}
//输出结果:
false
true

比较对象的值要用equals方法

从结果来看,比较1明显不是同事想要的结果,我们知道,比较两个Integer对象的值是否相等要用equals方法,不然使用==实际比较的是它们的地址,所以比较1改成如下形式就能得到我们想要的结果了:

System.out.println(a.equals(b));//true

PS: 因为a可能为null,会出现NPE空指针异常,所以除非确定了a不可能为null,否则更推荐使用jdk7.0起提供的java.util.Objects#equals(a,b)方法.


为什么a==1024又是true?

因为从Java 5.0起,引入了自动装箱和自动拆箱的操作,由于a == 1024中的1024是基本数据类型int,而a是包装类型Integer,当Integer和int比较时,Integer会被自动拆箱为int类型,此时是两个int相比较,所以比较的是值,相当于1024 == 1024,结果为true,其等价于:

Integer a = 1024;
int c = 1024;
//以下aa变量仅为了演示类型转变过程而加入的
int aa = Integer.parseInt(a);//相当于 int aa = 1024;
System.out.println(aa == c);

//输出结果:
true

总结

八大基本类型的包装类在比较2个对象值是否相等时使用equals方法,不推荐使用==进行比较。


问题还没结束…
一定要用equals吗?

接下来我们再看一段代码:

private static void test1() {
    Integer a = 100;
    Integer b = 100;
    System.out.println(a == b);
}
//输出结果:
true

不是说Integer比较值是否相等不能用==号,需要调用equals方法才行吗?现在只是把值从1024改为100而已,按照前面的解释,这个不应该输出的是false吗?为什么又是true


JDK缓存机制

在解决疑问之前,我们需要搞清楚一点,在我们使用Integer a = 100;这种形式进行初始化时发生了什么,我们来看一下这行代码反编译的结果:

BIPUSH 100
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1

通过对class文件反编译得知,其初始化过程隐式使用了Integer静态工厂方法valueOf进行自动装箱操作,即实际是Integer a = Integer.valueOf(100);


接下来我们查看Integer.valueOf方法的JDK源码:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Integer类里有一个静态内部类IntegerCache,顾名思义,我们先认为它是一个Integer的缓存类,继续查阅IntegerCache源码:

static final int low = -128;
static final int high;//注意这里没有明确赋予默认值
static final Integer cache[];

IntegerCache类有3个成员变量

  • IntegerCache.low=-128表示cache数组最小值为-128

  • IntegerCache.high默认情况下为127(特殊情况下文会讲解)

    我们的100介于-128和127之间,那么真相就在IntegerCache.cache数组里了,继续查看cache数组里到底存了什么,下面截取了cache数组的初始化源码:

cache = new Integer[(high - low) + 1];//数组容量为127+128+1,即256
int j = low;//j = -128
for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);
//数组cache下标[0,255]对应的值为[-128,127]

由此可知,当我们以Integer a = xxx;Integer a = Integer.valueOf(xxx)的形式初始化Integer时,如果xxx的取值范围是[-128,127],JDK会帮我们从缓存类IntegerCache里取出已经new好的Integer对象,所以我们一开始的Integer a = 100;Integer b = 100;都是指向同一个Integer对象,它们的地址自然是相等的,又因为使用==时比较的是两者的地址,所以它们比较的结果为true.


填坑:为什么IntegerCache类的high不是一开始就赋值了?

回归刚才的代码:

static final int low = -128;
static final int high;//注意这里没有明确赋予默认值
static final Integer cache[];

high初始化的代码如下,是写在静态代码块里的:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
	//high和cache的初始化过程
    static {
        // high value may be configured by property
        //缓存最大值可能通过JVM参数来配置
        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
                // 数组最大内容不能超过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.
                //JVM参数没配置或不可解析为int时high使用127
            }
        }
        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() {}
}

源码说明high是可以配置的,我们可以通过配置JVM参数-XX:AutoBoxCacheMax=<size>来改变缓存数组的上限值java.lang.Integer.IntegerCache.high,上限的取值范围最小为127,最大不能超过Integer.MAX_VALUE-128-1,即2^31-129 = 2147483519,这样保证了数组大小不能超过Integer.MAX_VALUE。


总结

JDK默认帮我们缓存了[-128,127]之间的值,我们可以配置JVM参数往上扩大上限的值,可扩展的范围为[128,2147483519],在显式或隐式使用Integer.valueOf初始化后,使用==比较得到的结果为true.

PS:当有人跟你说可以设置Integer缓存下限值时,别信!只能是-128

第一次发文,有不对或说不清楚的地方欢迎评论区指出,谢谢~


扩展知识:享元设计模式
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值