一. 问题展现
我们在面试的时候,面试官经常会给面试者洒出一些迷雾,用来迷惑面试者,这时往往就需要面试者掌握底层源码,才能对问题进行较好的回答。接下来壹哥就以Integer的缓存数组为例,通过分析其源码来教会大家该如何应对带有迷惑性的面试。
为了讲解清楚,壹哥给大家设计了一段代码如下,我们可以运行下面这段代码:
public class Test{
public static void main(String[] args){
Integer num1 = 100;
Integer num2 = 100;
System.out.println(num1==num2);
Integer num3 = 1000;
Integer num4 = 1000;
System.out.println(num3==num4);
}
}
上面这段代码中,其实就涉及到了关于Integer缓存机制相关的一些面试题,比如面试官会问我们,”你知道Integer的缓存机制吗“,”Integer.valueOf()方法的源码你熟悉吗?“,”int和Integer的区别有哪些“......
二. 结果分析
上面代码输出的结果应该是:
true
false
上面代码中有两个不一样的输出结果,本来明明以为是一样的结果,其实却不然!为什么这两个输出的结果一个是true,另一个却是false呢?
其实这里,num1和num2是在Integer的缓存数组中直接获取的整型缓存对象!而num3和num4却都是在直接new出来的Integer对象。至于为什么会这样,壹哥会结合Integer的源码对这个问题进行详细说明。
三. 源码解析
- 反编译结果
表面上看,上面的代码中都是把int类型的数值赋给了一个Integer引用类型。但是我们知道,一个基本的数据类型赋值给引用类型会进行装箱操作。那么Integer类又是如何将一个基本的int类型转变为引用类型的呢?具体过程到底如何呢?咱们有必要通过一些反编译工具进行反编译一些,上述代码反编译后得到的代码如下:
public class TestInt{
public TestInt(){
}
public static void main(String args[]){
Integer num1 = Integer.valueOf(100);
Integer num2 = Integer.valueOf(100);
System.out.println(num1 == num2);
Integer num3 = Integer.valueOf(1000);
Integer num4 = Integer.valueOf(1000);
System.out.println(num3 == num4);
}
}
- valueOf()源码分析
从反编译的结果中我们可以看到,反编译后的代码除了添加了一个默认的无参构造外,还将原来直接赋值的方式变成了调用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);
}
从源码中可以看到,valueOf方法的实现其实比较简单!我们很容易就能看出该方法的脉络,那就是如果i值在IntegerCache.low和IntegerCache.high范围之间,则返回数组中的一个对象;如果超过了这个范围,就会使用这个数值创建一个新的Integer对象并返回。
- IntegerCache源码
至于具体的执行情况如何,咱们还得打开IntegerCache这个类来查看。
private static class IntegerCache {
static final int low = -128;//缓存数组最小值设置为-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");
//判断如果这个值不是空,则把这个值和127比较取大值赋给high,同时对数组的范围进行
//了限制保证数组的长度不能超过int类型的最大值
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//获取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++);//把-128到high的数据一一赋进缓存数组中
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
从上面的源码中我们可以看到,IntegerCache这个类是Integer中的一个内部类。这个类里定义了一个Integer常量缓存数组cache,这个数组可以缓存-128到high这个变量之间的所有数据。这个high值,默认是127,但是我们也可以自己设置,但必须保证最大值至少要大于等于127,同时还要保证数组的长度不会超过整数类型的最大值,因为这个数组要缓存所有的-128到high之间的值。上面源码中,第23行代码到27行的代码,就是将-128到high之间的数通过new Integer()的方式生成Integer类型的对象,并放在数组中。
- valueOf再探究
接下来我们再回到valueOf方法来看一下:
可以看出,在默认情况下,100属于-128到127的范围,因此num1和num2都是使用的缓存数组中的同一个对象。而1000在缓存数组缓存的范围之外,因此是重新创建了两个Integer对象,并分别赋值给num3和num4,因此num1和num2比较时地址是相等的,而num3和num4的地址是不等的!
四. 知识扩展
- 修改high值
如果我们设置了high值是1000,应该看到num3==num4的结果也应该是true。我们可以在下面验证一下,这里要通过虚拟机参数设置high值,以eclipse为例,具体设置方法见下图:
- 再次执行
如上图,对high参数设置之后,high值变为了1000,我们点击run按钮重新运行,结果如下图:
可以看出,结果如咱们所料,此时两个结果均为true!
五. 总结
经过上面壹哥给大家的分析,你现在是不是已经对Integer的缓存机制有了深入的认识呢?最后咱们再把上述内容总结一下,看看该如何清晰地回答面试官的问题吧,对于这类面试题,我们可以这么回答:
在我们给Integer对象赋值时,其实是调用了Integer类的valueOf这个静态方法,而这个静态方法使用了Integer类中的IntegerCache内部类。
在IntegerCache这个内部类中存在一个Integer对象的缓存数组,这个数组中默认缓存了从-128到127的所有Integer对象。
当我们所赋的值在这个范围之间的时候,会直接从数组中获取Integer的缓存对象,因此两次100都是同一个对象。但是1000超出了这个范围,就会重新创建一个Integer对象,因此两次1000不是同一个对象。
至此,面试官的问题已经回答清楚了。如果你还记得住该如何设置high的最大值,也可以把最大值的设置过程给面试官讲讲,这个就属于锦上添花的回答了。
以上就是壹哥对Integer缓存机制面试题的分析过程,现在你知道该怎么去回答这个面试题了吗?欢迎大家在评论区给壹哥留言,说说你的感悟或疑惑吧。