基本数据类型及其包装类(二)

上篇文章我们简单介绍了包装的相关基本概念,并简单分析了 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 层面是没有变化的,这都是编译器做的「假象」。

image

只是编译器允许你这样书写代码了,其实编译成字节码指令的时候,编译器还是会调用相应的拆装箱方法的。

可以看到,拆装箱是需要方法调用的,也就是需要栈帧的入栈出栈的,直白点说,就是耗资源,所以我们的程序中应当尽量避免大量的「拆装箱」操作。

面试题

面试题一:

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)

欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。

image

posted @ 2018-04-14 19:25  Single_Yam  阅读(513)  评论(0编辑  收藏  举报