Java 为什么需要包装类
在 Java 的世界中,对象是一等公民,但 Java 也还是做出了妥协,出于对性能的考虑而保留了 8 种基础数据类型。
本文基于 JDK1.8
但是在某些场景下,无法直接使用基本数据类型,所以还是需要使用对象,Java 的包装类就是这样出现的。
自动装箱和拆箱
看下面的代码:
Java 编译器会自动把基本数据类型转成对象,这个称之为装箱。 到底是怎么做到的呢?看下面的字节码:
简单解释一下 invokestatic
和 invokevirtual
,这两个都是 JVM 的指令,前者表示调用 Java 的
静态方法,后者表示调用对象方法。
invokestatic 调用了 Integer.valueOf() 方法,所以装箱实际上就是调用了 Integer.valueOf()
方法。
拆箱也很简单,看下面的代码:
拆箱的字节码如下:
同理,拆箱实际调用的是 Integer 对象方法 i.intValue()
。
从上文可以看出,Java 中基本类型的装箱和拆箱实际上是编译器提供的语法糖,是在编译器层面进行处理的,编译器会将装箱和拆箱编译成调用方法的字节码。在虚拟机层,通过调用方法来实现包装类的装箱和拆箱。
Byte,Short,Long,Float,Double,Boolean,Character 与 Integer 类似。
但是需要注意,还有一个特殊的包装类 Void。 Void 是 void 的包装类,Void 不能被继承,也不能被实例化,仅仅就是一个占位符。
如果一个方法使用 void 修饰,说明方法没有返回值,如果使用 Void 修饰,则该方法只能返回 null。
Void 常用于反射中,判断一个方法的返回值是不是 void。
包装类的缓存
看下面的代码:
上面的代码应该算是一道经典的面试题了。通过上文可知,装箱操作使用的是 Integer.valueOf()
方法,源码如下:
关键实现在 IntegerCache 中,在某个范围内的数值可以直接使用已经创建好的对象。IntegerCache 是一个静态内部类,而且不能实例化,仅仅用来缓存 Integer 对象:
缓存对象的默认大小范围是 -128 ~ 127,正数范围可以根据自己的需要进行调整,负数最小就是 -128,不可以调整。如果不在这个范围内,就会创建新的对象。
上面代码的结果就很清晰了,第一个结果为 false 是因为 200 超出了默认的缓存范围,因此会创建新的对象。第二个结果为 false 是因为直接使用 new 来创建对象,而没有使用缓存对象。第三个结果为 true 是因为刚好在缓存的范围内。
所以在使用 Integer 等包装类生成对象时,不要使用 new 去新建对象,而应该尽可能使用缓存的对象,而且比较两个 Integer 对象时不要使用 ==,而应该使用 equals。
其他的包装类的实现基本类似,只是在对象缓存上的实现有些不同:
Byte 的范围刚好是 -128~127,所以都可以直接从缓存中获取对象。
Short 缓存范围也是 -128 ~ 127,而且不可以调整。
Long 的实现与 Short 一致。
Character 因为没有负数,所以缓存范围是 0 ~ 127,也不可以调整范围。
Boolean 的值只有 true 和 false,在类加载的时候直接创建好。
Float,Double 则没有缓存机制,因为是浮点数,可以表示无穷无尽的数,缓存的意义不大。
小心空指针
此外还需要注意的一点就是,使用包装类生成的是对象,是对象就有可能出现空指针异常,在代码中需要进行处理。
文 / Rayjun
本文首发于我的公众号 Rayjun,欢迎关注公众号,查看更多内容。
版权声明: 本文为 InfoQ 作者【Rayjun】的原创文章。
原文链接:【http://xie.infoq.cn/article/6b3549303fc5747df09eff35a】。文章转载请联系作者。
评论 (2 条评论)