|
| 1 | +本文主要介绍Java中的自动拆箱与自动装箱的有关知识。 |
| 2 | + |
| 3 | +## 基本数据类型 |
| 4 | + |
| 5 | +基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。 |
| 6 | + |
| 7 | +Java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。 |
| 8 | + |
| 9 | +Java基本类型共有八种,基本类型可以分为三类: |
| 10 | + |
| 11 | +> 字符类型`char` |
| 12 | +> |
| 13 | +> 布尔类型`boolean` |
| 14 | +> |
| 15 | +> 数值类型`byte`、`short`、`int`、`long`、`float`、`double`。 |
| 16 | +
|
| 17 | +数值类型又可以分为整数类型`byte`、`short`、`int`、`long`和浮点数类型`float`、`double`。 |
| 18 | + |
| 19 | +Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。 |
| 20 | + |
| 21 | +实际上,Java中还存在另外一种基本类型`void`,它也有对应的包装类 `java.lang.Void`,不过我们无法直接对它们进行操作。 |
| 22 | + |
| 23 | +### 基本数据类型有什么好处 |
| 24 | + |
| 25 | +我们都知道在Java语言中,`new`一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。 |
| 26 | + |
| 27 | +对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个Java对象的话,就会比较笨重。所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。 |
| 28 | + |
| 29 | +### 整型的取值范围 |
| 30 | + |
| 31 | +Java中的整型主要包含`byte`、`short`、`int`和`long`这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。 |
| 32 | + |
| 33 | +先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。 |
| 34 | + |
| 35 | +先来看计算中8bit可以表示的数字: |
| 36 | + |
| 37 | + 最小值:10000000 (-128)(-2^7) |
| 38 | + 最大值:01111111(127)(2^7-1) |
| 39 | + |
| 40 | + |
| 41 | +整型的这几个类型中, |
| 42 | + |
| 43 | +* byte:byte用1个字节来存储,范围为-128(-2^7)到127(2^7-1),在变量初始化的时候,byte类型的默认值为0。 |
| 44 | + |
| 45 | +* short:short用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。 |
| 46 | + |
| 47 | +* int:int用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。 |
| 48 | + |
| 49 | +* long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。 |
| 50 | + |
| 51 | +### 超出范围怎么办 |
| 52 | + |
| 53 | +上面说过了,整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码: |
| 54 | + |
| 55 | + int i = Integer.MAX_VALUE; |
| 56 | + int j = Integer.MAX_VALUE; |
| 57 | + |
| 58 | + int k = i + j; |
| 59 | + System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")"); |
| 60 | + |
| 61 | + |
| 62 | +输出结果:i (2147483647) + j (2147483647) = k (-2) |
| 63 | + |
| 64 | +**这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。**所以,在程序中,使用同类型的数据进行运算的时候,**一定要注意数据溢出的问题。** |
| 65 | + |
| 66 | +## 包装类型 |
| 67 | + |
| 68 | +Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。 |
| 69 | + |
| 70 | +包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示 |
| 71 | + |
| 72 | +| 基本数据类型 | 包装类 | |
| 73 | +| ------- | --------- | |
| 74 | +| byte | Byte | |
| 75 | +| boolean | Boolean | |
| 76 | +| short | Short | |
| 77 | +| char | Character | |
| 78 | +| int | Integer | |
| 79 | +| long | Long | |
| 80 | +| float | Float | |
| 81 | +| double | Double | |
| 82 | + |
| 83 | +在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。 |
| 84 | + |
| 85 | +### 为什么需要包装类 |
| 86 | + |
| 87 | +很多人会有疑问,既然Java中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢? |
| 88 | + |
| 89 | +这个问题,其实前面已经有了答案,因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。 |
| 90 | + |
| 91 | +为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。 |
| 92 | + |
| 93 | +## 拆箱与装箱 |
| 94 | + |
| 95 | +那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的int转换成一个包装类型的Integer对象。 |
| 96 | + |
| 97 | +我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于boxing,中文翻译为装箱。 |
| 98 | + |
| 99 | +反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于unboxing,中文翻译为拆箱。 |
| 100 | + |
| 101 | +在Java SE5之前,要进行装箱,可以通过以下代码: |
| 102 | + |
| 103 | + Integer i = new Integer(10); |
| 104 | + |
| 105 | + |
| 106 | +## 自动拆箱与自动装箱 |
| 107 | + |
| 108 | +在Java SE5中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。 |
| 109 | + |
| 110 | +自动装箱: 就是将基本数据类型自动转换成对应的包装类。 |
| 111 | + |
| 112 | +自动拆箱:就是将包装类自动转换成对应的基本数据类型。 |
| 113 | + |
| 114 | + Integer i =10; //自动装箱 |
| 115 | + int b= i; //自动拆箱 |
| 116 | + |
| 117 | + |
| 118 | +`Integer i=10` 可以替代 `Integer i = new Integer(10);`,这就是因为Java帮我们提供了自动装箱的功能,不需要开发者手动去new一个Integer对象。 |
| 119 | + |
| 120 | +## 自动装箱与自动拆箱的实现原理 |
| 121 | + |
| 122 | +既然Java提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java是如何实现的自动拆装箱功能。 |
| 123 | + |
| 124 | +我们有以下自动拆装箱的代码: |
| 125 | + |
| 126 | + public static void main(String[]args){ |
| 127 | + Integer integer=1; //装箱 |
| 128 | + int i=integer; //拆箱 |
| 129 | + } |
| 130 | + |
| 131 | + |
| 132 | +对以上代码进行反编译后可以得到以下代码: |
| 133 | + |
| 134 | + public static void main(String[]args){ |
| 135 | + Integer integer=Integer.valueOf(1); |
| 136 | + int i=integer.intValue(); |
| 137 | + } |
| 138 | + |
| 139 | + |
| 140 | +从上面反编译后的代码可以看出,int的自动装箱都是通过`Integer.valueOf()`方法来实现的,Integer的自动拆箱都是通过`integer.intValue`来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律: |
| 141 | + |
| 142 | +> 自动装箱都是通过包装类的`valueOf()`方法来实现的.自动拆箱都是通过包装类对象的`xxxValue()`来实现的。 |
| 143 | +
|
| 144 | +## 哪些地方会自动拆装箱 |
| 145 | + |
| 146 | +我们了解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了,那是最简单的也最容易理解的。 |
| 147 | + |
| 148 | +我们主要来看一下,那些可能被忽略的场景。 |
| 149 | + |
| 150 | +### 场景一、将基本数据类型放入集合类 |
| 151 | + |
| 152 | +我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢? |
| 153 | + |
| 154 | + List<Integer> li = new ArrayList<>(); |
| 155 | + for (int i = 1; i < 50; i ++){ |
| 156 | + li.add(i); |
| 157 | + } |
| 158 | + |
| 159 | + |
| 160 | +将上面代码进行反编译,可以得到以下代码: |
| 161 | + |
| 162 | + List<Integer> li = new ArrayList<>(); |
| 163 | + for (int i = 1; i < 50; i += 2){ |
| 164 | + li.add(Integer.valueOf(i)); |
| 165 | + } |
| 166 | + |
| 167 | + |
| 168 | +以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。 |
| 169 | + |
| 170 | +### 场景二、包装类型和基本类型的大小比较 |
| 171 | + |
| 172 | +有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码: |
| 173 | + |
| 174 | + Integer a=1; |
| 175 | + System.out.println(a==1?"等于":"不等于"); |
| 176 | + Boolean bool=false; |
| 177 | + System.out.println(bool?"真":"假"); |
| 178 | + |
| 179 | + |
| 180 | +对以上代码进行反编译,得到以下代码: |
| 181 | + |
| 182 | + Integer a=1; |
| 183 | + System.out.println(a.intValue()==1?"等于":"不等于"); |
| 184 | + Boolean bool=false; |
| 185 | + System.out.println(bool.booleanValue?"真":"假"); |
| 186 | + |
| 187 | + |
| 188 | +可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。 |
| 189 | + |
| 190 | +### 场景三、包装类型的运算 |
| 191 | + |
| 192 | +有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码: |
| 193 | + |
| 194 | + Integer i = 10; |
| 195 | + Integer j = 20; |
| 196 | + |
| 197 | + System.out.println(i+j); |
| 198 | + |
| 199 | + |
| 200 | +反编译后代码如下: |
| 201 | + |
| 202 | + Integer i = Integer.valueOf(10); |
| 203 | + Integer j = Integer.valueOf(20); |
| 204 | + System.out.println(i.intValue() + j.intValue()); |
| 205 | + |
| 206 | + |
| 207 | +我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。 |
| 208 | + |
| 209 | +### 场景四、三目运算符的使用 |
| 210 | + |
| 211 | +这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码: |
| 212 | + |
| 213 | + boolean flag = true; |
| 214 | + Integer i = 0; |
| 215 | + int j = 1; |
| 216 | + int k = flag ? i : j; |
| 217 | + |
| 218 | + |
| 219 | +很多人不知道,其实在`int k = flag ? i : j;`这一行,会发生自动拆箱。反编译后代码如下: |
| 220 | + |
| 221 | + boolean flag = true; |
| 222 | + Integer i = Integer.valueOf(0); |
| 223 | + int j = 1; |
| 224 | + int k = flag ? i.intValue() : j; |
| 225 | + System.out.println(k); |
| 226 | + |
| 227 | + |
| 228 | +这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。 |
| 229 | + |
| 230 | +因为例子中,`flag ? i : j;`片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候i的值为`null`,那么久会发生NPE。([自动拆箱导致空指针异常][1]) |
| 231 | + |
| 232 | +### 场景五、函数参数与返回值 |
| 233 | + |
| 234 | +这个比较容易理解,直接上代码了: |
| 235 | + |
| 236 | + //自动拆箱 |
| 237 | + public int getNum1(Integer num) { |
| 238 | + return num; |
| 239 | + } |
| 240 | + //自动装箱 |
| 241 | + public Integer getNum2(int num) { |
| 242 | + return num; |
| 243 | + } |
| 244 | + |
| 245 | + |
| 246 | +## 自动拆装箱与缓存 |
| 247 | + |
| 248 | +Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果: |
| 249 | + |
| 250 | + public static void main(String... strings) { |
| 251 | + |
| 252 | + Integer integer1 = 3; |
| 253 | + Integer integer2 = 3; |
| 254 | + |
| 255 | + if (integer1 == integer2) |
| 256 | + System.out.println("integer1 == integer2"); |
| 257 | + else |
| 258 | + System.out.println("integer1 != integer2"); |
| 259 | + |
| 260 | + Integer integer3 = 300; |
| 261 | + Integer integer4 = 300; |
| 262 | + |
| 263 | + if (integer3 == integer4) |
| 264 | + System.out.println("integer3 == integer4"); |
| 265 | + else |
| 266 | + System.out.println("integer3 != integer4"); |
| 267 | + |
| 268 | + } |
| 269 | + |
| 270 | + |
| 271 | +我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,==比较的是对象应用,而equals比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。 |
| 272 | + |
| 273 | +上面这段代码真正的输出结果: |
| 274 | + |
| 275 | + integer1 == integer2 |
| 276 | + integer3 != integer4 |
| 277 | + |
| 278 | + |
| 279 | +原因就和Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 |
| 280 | + |
| 281 | +> 适用于整数值区间-128 至 +127。 |
| 282 | +> |
| 283 | +> 只适用于自动装箱。使用构造函数创建对象不适用。 |
| 284 | +
|
| 285 | +具体的代码实现可以阅读[Java中整型的缓存机制][2]一文,这里不再阐述。 |
| 286 | + |
| 287 | +我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。 |
| 288 | + |
| 289 | +其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过`-XX:AutoBoxCacheMax=size`修改。 |
| 290 | + |
| 291 | +实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过`java.lang.Integer.IntegerCache.high`设置最大值。 |
| 292 | + |
| 293 | +这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。 |
| 294 | + |
| 295 | +在Boxing Conversion部分的Java语言规范(JLS)规定如下: |
| 296 | + |
| 297 | +如果一个变量p的值是: |
| 298 | + |
| 299 | + -128至127之间的整数(§3.10.1) |
| 300 | + |
| 301 | + true 和 false的布尔值 (§3.10.3) |
| 302 | + |
| 303 | + ‘\u0000’至 ‘\u007f’之间的字符(§3.10.4) |
| 304 | + |
| 305 | + |
| 306 | +范围内的时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。 |
| 307 | + |
| 308 | +## 自动拆装箱带来的问题 |
| 309 | + |
| 310 | +当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。 |
| 311 | + |
| 312 | +> 包装对象的数值比较,不能简单的使用`==`,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用`equals`比较。 |
| 313 | +> |
| 314 | +> 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。 |
| 315 | +> |
| 316 | +> 如果一个for循环中有大量拆装箱操作,会浪费很多资源。 |
| 317 | +
|
| 318 | +## 参考资料 |
| 319 | + |
| 320 | +[Java的自动拆装箱][3] |
| 321 | + |
| 322 | + [1]: http://www.hollischuang.com/archives/435 |
| 323 | + [2]: http://www.hollischuang.com/archives/1174 |
| 324 | + [3]: https://www.jianshu.com/p/cc9312104876 |
0 commit comments