裝箱與拆箱
裝箱與拆箱
什麽是裝箱與拆箱
描述
語言描述,裝箱就是自動將基本數據類型轉換為包裝器類型;拆箱就是自動將包裝器類型轉換為基本數據類型。
代碼描述就是:
Integer integer = 100; //自動裝箱
int i = integer; //自動拆箱
基本技術類型對應的包裝器類型表:
數據類型 | 包裝器類型 |
---|---|
int(4字節) | Integer |
byte(1字節) | Byte |
short(2字節) | Short |
long(8字節) | Long |
float(4字節) | Float |
double(8字節) | Double |
char(2字節) | Character |
boolean(未定) | Boolean |
如何實現裝箱與拆箱
裝箱與拆箱的代碼
public class IntegerAndInt {
public static void main(String[] args) {
// TODO Auto-generated method stub
Integer integer = 100; //自動裝箱
int i = integer; //自動拆箱
}
}
反編譯class文件
從反編譯得到的字節碼內容可以看出,在裝箱的時候自動調用了Integer的valueOf(int)方法。而在拆箱的時候自動調用的是Integer的intValue方法。
因此,裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的xxxValue方法實現的。
valueOf方法
public static Integer valueOf(int i) {
return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}
首先判斷數值大小,如果數值大於等於128或者小於-128,則創建一個Integer對象返回,否則,則返回SMALL_VALUES的i+128的數值。
接著,查看以下Integer的構造函數:
private final int value; public Integer(int value) { this.value = value; } public Integer(String string) throws NumberFormatException { this(parseInt(string)); }
Integer定義了一個value變量,創建一個Integer對象時,就會給這個變量初始化。第二個傳入的是一個String變量,它會先把它轉換成一個int值,然後進行初始化。
再看一下SMALL_VALUES[i+128]是什麽:
private static final Integer[] SMALL_VALUES = new Integer[256];
SMALL_VALUES是一個靜態的Integer數組對象,也就是說valueOf返回的都是一個Integer對象。
通過分析可以看到裝箱的過程會創建對應的對象,這個會消耗內存,所以裝箱的過程會增加內存的消耗,影響性能。
initValue方法
@Override
public int intValue() {
return value;
}
intValue方法直接返回了value值。
裝箱與拆箱需要註意的一些問題
例子一
public class Main{
public static void main(String[] args) {
Integer i1 = 66, i2 = 66, i3 = 166, i4 = 166;
System.out.println(i1 == i2);//true
System.out.println(i3 == i4);//false
}
}
看上面的代碼可以發現,兩個比較的結果不相同,再結合上面的裝箱原理,128~-127的裝箱是直接返回的SMALL_VALUES數組中存儲的值,所以i1和i2的裝箱結果返回的是同一個變量,所以是相等的,而i3和i4是返回的新創建的變量,兩個變量是不同的,所以不相等。
例子二
Integer a = new Integer(6);
Integer b = 6; // 將6自動裝箱成Integer類型
int c = 6;
System.out.println(a == b); // false 兩個引用沒有引用同一對象
System.out.println(a == c); // true a自動拆箱成int類型再和c比較
a是一個創建的Integer對象,而b是自動裝箱,是SMALL_VALUES數組中存儲的值,所以a和b是不同的對象,不相等,而c是int類型的值,a與c相比是會先拆箱為int類型的6,兩個6數值相比,故相等。
例子三
Integer i1 = new Integer(6);
Integer i2 = 6;
System.out.println("i1.equals(i2):"+(i1.equals(i2))); //true
會發現同樣的對象,==與equals的結果是不同,先看一下equals源碼:
@Override
public boolean equals(Object o) {
return (o instanceof Integer) && (((Integer) o).value == value);
}
發現equals方法是比較value值相同的,比較的內容的本身。
例子四
Integer num1 = 100;
int num2 = 100;
Long num3 = 200L;
System.out.println(num1+num2); //200
System.out.println(num3 == (num1+num2)); //true
System.out.println(num3.equals(num1+num2)); //false
System.out.println(num3 == (num1+num2)); //true
當一個基本類型數據域封裝類進行==、+、-、*、/運算時,會將封裝類進行拆箱,對基礎數據類型進行運算。
而num3.equals(num1+num2)為false的原因是,num1+num2的類型不是Long,所以為false。
Long的equals方法:
@Override
public boolean equals(Object o) {
return (o instanceof Long) && (((Long) o).value == value);
}
而(num3 (num1+num2))為true,當運算符的兩個操作數都是包裝器類型的引用,則是比較指向的是否是同一個對象,而如果其中有一個操作數是表達式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。
例子五
Integer integer=null;
int i=integer;
上面的代碼可以通過編譯,但是在運行時,就會拋出空指針異常。integer是Integer類型的對愛選哪個,可以指向null,但是對integer進行拆箱的時候,就是對一個null對象調用valueOf方發,所以會拋出空指針異常。
註意
Integer、Short、Byte、Charater、Long這幾個類的valueOf方法的實現是類似的,Double和Float的valueOf方法的實現是類似的,是沒有想Integer一樣的SMALL_VALUE數組的。
Double的valueOf方法:
public static Double valueOf(double d) {
return new Double(d);
}
Float的valueOf方法:
public static Float valueOf(String s) throws NumberFormatException {
return new Float(FloatingDecimal.getThreadLocalInstance().readJavaFormatString(s).floatValue());
}
還有,Boolean的valueOf方法:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
所以不管對象是不是同一個,只要對象的值時相同的,就是相等的。
5.總結
Java通過自動裝箱和拆箱的機制,節省了部分內存開銷和創建對象的開銷(Integer的128~-127常用值節省開支),提高了效率同時簡化了代碼,不用每次需要程序員轉換類型。在比較數值相等時,盡量使用equals()方法。
參考文章
https://www.cnblogs.com/wang-yaz/p/8516151.html 詳解Java的自動裝箱與拆箱(Autoboxing and unboxing)
https://www.cnblogs.com/dolphin0520/p/3780005.html 深入剖析Java中的裝箱和拆箱
裝箱與拆箱