【Java踩坑系列】01:Integer
The Integer class wraps a value of the primitive type int in an object. An object of type Integer contains a single field whose type is int.
Integer 类的介绍很简单,使用也一样简单,一般使用也不会出现什么问题,当然文章标题既然是踩坑系列,就说说自己工作中遇到过的关于Integer的坑吧。
背景:上家公司是传统行业,所在的部门开发了一款法院庭审系统,新版本发布前都经过严格的测试(考虑到软件的应用现场是法院等一些国家单位基于数据保密性等的原因现场测试难度较大),由于从公司版本发布到真正软件在现场环境运行可能是N个月之后,出现问题也难于排查,只能在公司环境进行复现,然后修改BUG。
问题出现:软件部署到现场,经过演示,测试一切顺利,法院开始使用,但是使用一段时间之后出现无法开庭的情况。
问题定位:进过调试,复现,最终问题定位在Integer比较相等的一段代码上。类似于下面的这段代码:
if(userLogin.getId() == userSjy.getId()){ // 允许开庭 }
逻辑很简单,就是当前的用户id和书记员id相等的时候允许开庭,否则不允许开庭。而这里的User.id 的类型是Integer,或许你会说很明显应该用 equals() 方法,但就是有人写了这样的代码,而且没有测试出来,发布了,而且在现场运行了一段时间之后才爆出了问题。
问题反思:问题是诡异之处在于,系统刚开始是正常的,经过一段时间的运行才出现问题。说明有什么东西发生了改变,而发生改变的东西显然不是代码本身,那到底是什么发生改变导致了这样诡异的的BUG呢?问题出在Integer本身。请看如下代码:
Integer a = 1;
Integer b = 1;
System.out.println(a.equals(b)); // true
System.out.println(a==b); // true
还有如下代码:
Integer a = 127;
Integer b = 127;
System.out.println(a.equals(b)); // true
System.out.println(a==b); // true
最后如下代码:
Integer a = 127+1;
Integer b = 127+1;
System.out.println(a.equals(b)); // true
System.out.println(a==b); // false <<---
出现了诡异的 false ,而且数值定位在了 127+1,而这里到底发生了什么?为什么会出现这样的现象?
问题原因:我们使用Java自带的反编译工具对这段代码进行反编译,看看Java编译器对这段代码做了什么,命令行输入
javap -v IntegerTest.class
跳过栈信息,输出如下:
反编译class文件最重要的信息:
0: sipush 128
3: invokestatic #16 // Method java/lang/Integer.valueOf(I)Ljava/lang/Integer;
6: astore_1
7: sipush 128
10: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
翻译一下:
Integer a = 127+1; // Integer a = Integer.valueOf(128);
Integer b = 127+1; // Integer b = Integer.valueOf(128);
所以,看似你直接给 a 和 b 赋值 ,其实是编译器帮你做了一些事情,调用了 Integer.valueOf() 方法,而在Integer.valueOf()方法里面到底发生了什么呢?
Integer.valueOf()方法IntegerCache 是什么?显然它是Java为了提高性能所做的一种缓存机制,缓存的下界是-128它是final的,默认的上界是127不是final的,JVM启动的时候就把这些值缓存起来了,看代码貌似上界是可以设置下界的值?
Integer.IntegerCache当然可以改变缓存值的上界,为什么下界无法改变?可能是因为没必要吧,因为负数使用的频率确实很低不是么?JVM 启动参数决定缓存的上界大小。
而很显然,启动参数的名称没有写成 IntegerAutoBoxCacheMax,说明是全局的参数,奇怪的是看了下Long的源代码貌似并没有使用这个参数 ~_~ ,而是写死了-128~127
问题最终原因算是找到了,因为Java对小数值的Integer进行了缓存(使用频率高),以提高Java的效率,造成的现象就是在系统运行的起初阶段(包括测试阶段)在书记员id还很小的时候,== 的效果和equals一样,而后来随着id的不断增大(也可能书记员id不会超过127,问题就一直不暴露出来),达到128乃至更大的的时候,问题就必定暴露出来。
总结:当然这个坑算是填上了,在比较Integer/Long 等基本类型包装类是否相等的的时候的时候不要直接使用==去比较,而要使用equals方法。说的再笼统一点就是比较对象类型相等的时候请使用equals方法。