title: “java 自動裝拆箱”
tags:
- Java
將基本數據類型封裝成對象的過程叫做裝箱(boxing),反之基本數據類型對應的包裝類轉換為基本數據類型的過程叫做拆箱(unboxing);
基本數據類型與其他對象的區別
基本數據類型
Java是一門面向對象的強類型語言,但它又不像python那樣一切皆對象,Java中有一部分使用最頻繁的數據結構并不是面向對象的,他們就是基本數據類型,也叫內置類型,他們在棧中儲存,比起其他用new
創建的對象,更加高效,Java有9中基本數據類型,分為五類
類型 | 標識符 | 備注 |
---|---|---|
整型 | byte, short, int, long | |
浮點 | float, dauble | |
字符 | char | |
布爾 | boolean | |
空 | void | 不能操作 |
基本數據類型的范圍
整型的范圍
- byte: 占一個字節,也就是8位,最高一位作為符號位,有效位只有7位(采用補碼存儲)。
最大值:0,111 1111(127)
最小值:1,000 0000(-128)
怎么算的?
最高一位是符號位,這是固定的,正數用0表示,負數用1表示,然后后面的七位最大是7個1,最小是7個0,這就是用補碼表示的byte能表示的最大最小數,把補碼轉換為原碼(正數的補碼就是源碼,負數的補碼變源碼取反加一)然后轉換為10進制。
- short:占兩個字節,16位,有效15位
最大值:2^15 -1: 32,767
最小值:-(2^15): -32,768
- int: 占4個字節,最大值【2^31 - 1】(2,147,483,647), 最小值【-2^31】(-2,147,483,648)
- long: 占8字節,最大值【2^63 - 1】(9,223,372,036,854,775,807),最小值【-2^63】(-9,223,372,036,854,775,808)
包裝類型
Java中其他的對象都是繼承自object的,有自己的屬性和方法,為了方便基本基本數據類型和其他對象的的操作,Java為每個基本數據類型提供了對應的包裝類型,
基本數據類型 | 包裝類 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
- 為什么要使用包裝類型
Java是一門面向對象的語言,大部分操作都是針對對象的,比如容器,容器中能存入的最大的范圍就是object,而基本數據類型不屬于對象,那他就無法存入容器,為了解決這個問題,必須把基本數據類型“包裝”起來,讓他作為一個對象參與到編程中。
裝箱和拆箱
把基本數據類型包裝成對象的過程叫做裝箱,反之把對象轉換為基本數據類型的過程叫做拆箱。
Java SE5 后,為了簡化開發,提供了自動裝拆箱機制,Java會在適當時刻自動轉換基本數據類型和包裝類型,如:
public class Demo1 {public static void main(String[] args) {Integer integer = 3; // 自動裝箱int i = integer; // 自動拆箱}
}
通過Javap反編譯得到
public static void main(java.lang.String[]);Code:0: iconst_31: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: astore_15: aload_16: invokevirtual #3 // Method java/lang/Integer.intValue:()I9: istore_210: return
在自動裝箱時,其實是調用了包裝類的valueOf()
方法,而在自動拆箱時則調用了包裝類的intValue()
方法,所以如果在JavaSE5之前,沒有自動裝拆箱機制,上面的代碼我們需要這樣寫
public class Demo1 {public static void main(String[] args) {Integer integer = Integer.valueOf(3); // 裝箱int i = Integer.intValue(integer); // 拆箱}
}
除int和Integer之外,其他基本類型和包裝類的自動轉換也一樣,裝箱時調用valueOf()
方法,拆箱時調用xxxValue()
方法。
什么時候自動裝箱
1. 初始化,賦值,函數返回時
當把基本數據類型賦值給包裝類時或者基本數據類型作為函數返回值但函數聲明要求返回包裝類型時,會自動裝箱,如上面的例子
2. 將基本數據類型放入容器中
public void func2(){List<Integer> list = new ArrayList<>();list.add(1);}
反匯編之后
public void func2();Code:0: new #4 // class java/util/ArrayList3: dup4: invokespecial #5 // Method java/util/ArrayList."<init>":()V7: astore_18: aload_19: iconst_110: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;13: invokeinterface #6, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z18: pop19: return
在第10步,使用了自動裝箱
什么時候自動拆箱
1. 初始化,賦值,函數返回時
把包裝類對象賦值給基本數據類型的變量時,會自動拆箱(函數返回值原理一樣)
2. 包裝類型做算數運算時
算數運算(包括比較大小)是針對基本數據類型的,所以無論是基本數據類型與包裝類型還是包裝類型與包裝類型之間做運算都會轉換成兩個基本數據類型
public void func3(){Integer integer = 3;int i = 1;Integer integer1 = 1;boolean b1 = integer > i; // 基本數據類型與包裝類型比較大小boolean b2 = integer > integer1; // 兩個包裝類型比較大小}
反匯編后
public void func3();Code:0: iconst_31: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: astore_15: iconst_16: istore_27: iconst_18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;11: astore_312: aload_113: invokevirtual #3 // Method java/lang/Integer.intValue:()I16: iload_217: if_icmple 2420: iconst_121: goto 2524: iconst_025: istore 427: aload_128: invokevirtual #3 // Method java/lang/Integer.intValue:()I31: aload_332: invokevirtual #3 // Method java/lang/Integer.intValue:()I35: if_icmple 4238: iconst_139: goto 4342: iconst_043: istore 545: return
通過13步,說明基本數據類型與包裝類型比較大小會轉換為兩個基本數據類型再比較
通過28,32步,說明兩個包裝類型比較大小也會轉換為基本數據類型
普通的加減乘除也一樣
public void func4() {Integer integer = 3;int i = 1;Integer integer1 = 1;int s = integer + integer1;int s1 = integer + i;}
public void func4();Code://...13: invokevirtual #3 // Method java/lang/Integer.intValue:()I16: aload_317: invokevirtual #3 // Method java/lang/Integer.intValue:()I20: iadd21: istore 423: aload_124: invokevirtual #3 // Method java/lang/Integer.intValue:()I//...
3. 三目運算
如果三目運算的第二三位一個是基本數據類型另一個是包裝類型時,會自動拆箱成兩個基本數據類型
public void func5() {boolean flag = true;Integer i = 8;int j;j = 3;int k = flag ? i: j;
}
public void func5();Code:0: iconst_11: istore_12: bipush 84: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;7: astore_28: iconst_39: istore_310: iload_111: ifeq 2114: aload_215: invokevirtual #3 // Method java/lang/Integer.intValue:()I18: goto 2221: iload_322: istore 424: return
因為i是包裝類型,j是基本數據類型,所以在14行把i自動拆箱成了基本數據類型(并不是應為三目運算返回的是int),所以做三目運算時應該注意,尤其是基本數據類型和對象混雜時,如果對象沒被賦值,可能導致NPL(空指針異常)
只有一個是基本數據類型,一個是包裝類對象時才會自動拆箱,兩個對象是不拆的。
裝箱和拆箱時的緩存問題
public class Demo2 {public static void main(String[] args) {Integer a1 = 1;Integer a2 = 1;int a3 = 1;System.out.println(a1 == a2); // trueSystem.out.println(a1.equals(a2)); // trueSystem.out.println(a1 == a3); // trueSystem.out.println(a1.equals(a3)); // true}
}
public class Demo2 {public static void main(String[] args) {Integer a1 = 133;Integer a2 = 133;int a3 = 133;boolean b1 = a1 == a2;boolean b2 = a1.equals(a2);boolean b3 = a1 == a3;boolean b4 = a1.equals(a3);System.out.println(b1); // falseSystem.out.println(b2); // trueSystem.out.println(b3); // trueSystem.out.println(b4); // true}
}
兩次結果不同,原因就是自動拆裝箱時存在緩存問題,當我們第一次使用Integer時,Java會初始化一個Integer[] cache
然后通過循環把-128到127之間的數加入到這個緩存中,如果新new的Integer的值在這個范圍內,就直接返回這個創建好的對象,equals()
比較值,==
比較是不是同一對象,所以不管怎樣,equals的結果都是true,而==
在-128 到 127 之間是true,超出這個范圍是false。
這個就類似于python中的小整數池,但python的范圍是[-5, 256]
除[-128, 127]之間的整數外,boolean的兩個值,以及\u0000
至 \u007f
之間的字符也在常量池中。
總結
- 什么是包裝類
為了方便操作基本數據類型,對每一種基本類型提供一個包裝類,他們將基本數據類型包裝成一個對象
- 什么是裝箱,拆箱
把基本數據類型包裝成包裝類的過程叫裝箱(使用包裝類的valueOf()方法
)
把包裝類轉換為基本數據類型的過程叫拆箱(使用包裝類的xxxValue()方法
)
- 什么是自動裝箱/拆箱
Java SE5 引入的一種在特定情況下將基本數據類型自動轉換為包裝類/包裝類自動轉換為基本數據類型 的機制
-
什么時候自動裝箱
-
Integer a = 5
-
函數返回值
-
把基本數據類型加入容器時
-
什么時候自動拆箱
-
初始化,賦值, 函數返回值
-
三目運算第二第三個參數既有包裝類,也有基本數據類型時
-
算術運算,比較大小
-
什么是自動拆裝箱時的緩存
第一次使用某些包裝類時,Java會創建一個緩存池,以后每次需要包裝類對象,就先會找緩存池中有沒有,有就直接返回,沒有才創建。緩存池的范圍:
整型:[-128, 127]
布爾:[true, false]
字符:[\u0000
, \u007f
]
參考
什么是Java中的自動拆裝箱
Java中整型的緩存機制