关于自动装箱的这几道面试题,你都能答对吗?
程序员文章站
2022-06-12 20:37:53
...
面试题
如果之前学习过自动装箱的知识,可以先做下面几道面试题,帮助我们发现自己的知识盲点。如果你没有了解过自动装箱,可以先跳到后面阅读知识点总结部分,再回来做题。
面试题一
public void test() {
//1
int a = 100;
Integer b = 100;
System.out.println(a == b);// true
//2
Integer c = 100;
Integer d = 100;
System.out.println(c == d);// true
//3
c = 200;
d = 200;
System.out.println(c == d);// false
}
- 第1段代码,基础类型a与包装类b进行==比较,这时b会拆箱,直接比较值,所以答案会打印 true
- 第2段代码,两个包装类型变量都被赋值了100,所以根据我们之前的解析,这时会进行装箱,调用Integer的valueOf方法,生成2个Integer对象,引用类型==比较,直接比较对象指针,因为默认Integer cache 的下限是-128,上限默认127,这个范围的Integer会被缓存,所以在这里是同一个对象。答案为打印 true。
- 跟上面第2段代码类似,只不过赋值变成了200,不在Integer的缓存范围,所以并不是同一个对象了。答案为打印 false
面试题二
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false
System.out.println(i3==i4);//false
}
在这里解释一下为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单,因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。也就是说,Double和Float的valueOf方法始终返回新对象。
注意:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。
面试题三
面试题三涉及的知识点在上面都介绍过了,先尝试一下不看答案的情况下,自己能全部回答正确吗。
Integer a=1;
Integer b=2;
Integer c=3;
Integer d=3;
Integer e=321;
Integer f=321;
Long g=3L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
答案:
true
false
true
true
true
false
知识点总结
拆箱与装箱的缓存问题
- 整型(Byte,Short,Integer,Long)会检查该数字是否在1个字节可表示的有符号整数范围内(-128~127),是则返回缓存对象,否则返回新对象。
- Character会缓存整型值为0~127的字符,同样会检查字符是否落在缓存范围中,是则返回,否则返回新对象。
- Double和Float的valueOf方法始终返回新对象。
触发自动装箱或拆箱的场景
- 进行 = 赋值操作(装箱或拆箱)
- 进行+,-,*,/混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象
- 而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)
- 调用equals进行比较(装箱)
- ArrayList,HashMap等集合类 添加基础类型数据时,因为集合只能添加引用类型(装箱)
装箱与拆箱的实现原理
以Float为例
- 调用了Float类的静态 valueOf方法,进行装箱
- 调用了Float的floatValue方法,进行拆箱
自动装箱的性能问题
自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,会创建多余的对象,影响程序的性能。
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
上面的代码sum+=i
可以看成sum = sum + i
,但是+
这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下
int result = sum.intValue() + i;
Integer sum = new Integer(result);
由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作。
- 自动装箱会创建多余对象,影响性能
- 自动装箱成包装类型,需要记得判空,否则容易出现空指针异常
补充:基本类型和包装类型的使用场景
- 集合类使用时只能使用包装类型。
- 如果允许null值,则必然要用封装类
- 用到泛型和反射调用函数,就需要用包装类
- 其他情况下,最好使用基本类型,能避免自动装箱(需要new对象)的性能消耗,特别是在循坏中使用包装类型自动装箱会new出很多对象