上篇文章我们简单介绍了包装的相关基本概念,并简单分析了 Integer 类中的几个核心的方法源码,但是有关自动拆装箱的概念限于篇幅并没能完成介绍,本篇还将分析几种常见的包装类面试题,深入理解一下我们的包装类设计。
自动拆装箱
所谓「拆箱」就是指,包装类型转换为基本类型的过程,而所谓的「装箱」则是基本类型到包装类型的过程。例如:
public static void main(String[] args){
int age = 21;
Integer integer = new Integer(age); //装箱
int num = integer.intValue(); //拆箱
}
自从 jdk1.5 以后,引入了自动拆装箱的概念,上述代码可以简化成如下代码:
public static void main(String[] args){
int age = 21;
Integer integer = age; //自动装箱
int num = integer; //自动拆箱
}
是不是感觉简便了很多,但是实际上在 JVM 层面是没有变化的,这都是编译器做的「假象」。
只是编译器允许你这样书写代码了,其实编译成字节码指令的时候,编译器还是会调用相应的拆装箱方法的。
可以看到,拆装箱是需要方法调用的,也就是需要栈帧的入栈出栈的,直白点说,就是耗资源,所以我们的程序中应当尽量避免大量的「拆装箱」操作。
面试题
面试题一:
public static void main(String[] args){
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
如果之前没了解过 Integer 内部源码的人想必会对输出的结果「百思不得其解」。
输出结果为:
true
false
如果你认真看完了我的两篇文章,这个问题应该不难解释。
直接将整型数值赋值给 Integer 实例将发生装箱操作,也就是调用 valueOf 方法,而这个方法我们分析过,会首先检查一下 100 是否在缓存池是否缓存了,当然 IntegerCache 会默认缓存 [-128,127] 之间的 Integer 实例,所以这里会直接从缓存池中取出引用赋值给变量 i1 。
同理 i2 也会从缓存池中取引用,并且两者的引用的是同一个堆对象,所以才会输出 「true」。
而第二个输出「false」也是很好理解的,因为 200 不再缓存池缓存的范围内,所以每次调用 valueOf 方法都会新建一个不同的 Integer 实例。
面试题二:
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);
System.out.println(i3==i4);
}
很多人会认为这段代码的输出结果会和上题一样,但是其实不然:
false
false
那是因为 Double 这个包装类并没有缓存池的概念,也就是说它会为每一个 double 型数值包装一个新的 Double 实例。正如它的 valueOf 方法:
public static Double valueOf(double d) {
return new Double(d);
}
这里可能有人会疑问了,为什么 Integer 用缓存池提升效率,而 Double 却弃之不用呢?
其实也很简单,你会发现 IntegerCache 是用 Integer 数组缓存了某个区间的所有数值对应的 Integer 实例,那么请问给定一个区间 [-128.0,127.0],你能确定之中有多少个 double 数值吗?
因为任意一个区间,哪怕再小的区间都对应的无穷尽的 double 小数,所以无法进行缓存。
同样的问题也适用于 Float。
面试题三:
public static void main(String[] args){
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
输出结果:
true
true
Boolean 的 valueOf 方法是这样的:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
显然,结果相信不再需要多做解释了。
最后需要提一下的是,八种包装类中有以下五种是支持「缓存池」的。
- Integer:对应的缓存池类型为 IntegerCache
- Byte:对应的缓存池类型为 ByteCache
- Short:对应的额缓存池类型为 ShortCache
- Long:对应的额缓存池类型为 LongCache
- Character:对应的缓存池类型为 CharacterCache
其实 Boolean 的实现比较特殊,因为它只有两种取值可能,其实也能够算作支持缓存功能的。
文章中的所有代码、图片、文件都云存储在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。