遛马少年,一个代码写的很6的程序员,专注于技术干货分享
最近,在处理线上bug的时候,发现了一个奇怪的现象
业务代码大概是这样的
public static boolean doSth(Integer x, Integer y) { if (x == y) { return true; } //do other... return false; }
当x、y都是较小的值时,比如100、100,正常返回true
当是较大值时,比如500、500,反而返回false
难道100==100,500!=500吗?
带着这样的疑问,我写了个demo程序一探究竟
public class IntDemo { public static boolean doSth(Integer a, Integer b) { if (a == b) { return true; } return false; } public static void main(String[] args) { int a = 100; int b = 500; System.out.println(doSth(a, a)); System.out.println(doSth(b, b)); } }
输出结果为:
奇怪!底层是怎么处理的呢?我用javap看了一下上面代码的字节码指令
public class com.integer.IntDemo { public com.integer.IntDemo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static boolean doSth(java.lang.Integer, java.lang.Integer); Code: 0: aload_0 1: aload_1 2: if_acmpne 7 5: iconst_1 6: ireturn 7: iconst_0 8: ireturn public static void main(java.lang.String[]); Code: 0: bipush 100 2: istore_1 3: sipush 500 6: istore_2 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_1 11: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 14: iload_1 15: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 18: invokestatic #4 // Method doSth:(Ljava/lang/Integer;Ljava/lang/Integer;)Z 21: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V 24: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 27: iload_2 28: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 31: iload_2 32: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 35: invokestatic #4 // Method doSth:(Ljava/lang/Integer;Ljava/lang/Integer;)Z 38: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V 41: return }
可以看到,doSth函数传入的实参是int类型,函数定义的形参却是Integer类型
看到第11行字节码指令我就懂了,原来是通过Integer.valueOf 来做的一个int的自动装箱
11: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
所以,问题肯定出在Integer.valueOf里面,接着,我点开valueOf的源码
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
好家伙,这里用到了一个缓存类:IntegerCache
判断如果在缓存范围内,直接返回这个缓存类持有的引用,否则就new一个Integer对象
再点开这个缓存类,low=-128,high=127
这就解释了为什么100是true,500是false了
JDK为什么要设计这样一个很容易掉进去的坑呢?
其实,在valueOf方法上,官方已经给出了说明:
/** * Returns an {@code Integer} instance representing the specified * {@code int} value. If a new {@code Integer} instance is not * required, this method should generally be used in preference to * the constructor {@link #Integer(int)}, as this method is likely * to yield significantly better space and time performance by * caching frequently requested values. * * This method will always cache values in the range -128 to 127, * inclusive, and may cache other values outside of this range. * * @param i an {@code int} value. * @return an {@code Integer} instance representing {@code i}. * @since 1.5 */
大概意思就是,-128~127 的数据在 int 范围内是使用最频繁的,为了减少频繁创建对象带来的内存消耗,这里其实是用到了享元模式,以提高空间和时间性能。
既然Integer这样设计了,其他类会不会也有呢?
接着,我又看了其他数据类型,用缓存的还不少,这里我给各位列一下,防止你们以后踩坑
基本类型 | 包装类型 | 缓存范围 |
---|---|---|
boolean | Boolean | - |
byte | Byte | -128-127 |
short | Short | -128-127 |
int | Integer | -128-127 |
long | Long | -128-127 |
float | Float | - |
double | Double | - |
小伙伴们在开发过程中,也要注意,避免掉进这个坑里。
好了,今天的分享就到这里了,如果你觉得有用,麻烦给兄弟点个小赞,这样我才更有动力去分享更多技术干货~