JAVA深化篇_03——包裝類與自動拆裝箱及緩存問題
基本數據類型的包裝類
我們前面學習的八種基本數據類型并不是對象,為了將基本類型數據和對象之間實現互相轉化,Java為每一個基本數據類型提供了相應的包裝類。
包裝類基本知識
Java是面向對象的語言,但并不是“純面向對象”的,因為我們經常用到的基本數據類型就不是對象。但是我們在實際應用中經常需要將基本數據轉化成對象,以便于操作。比如:將基本數據類型存儲到Object[ ]數組或集合中的操作等等。
為了解決這個不足,Java在設計類時為每個基本數據類型設計了一個對應的類進行代表,這樣八個和基本數據類型對應的類統稱為包裝類(Wrapper Class)。
包裝類位于java.lang包,八種包裝類和基本數據類型的對應關系:
| 基本數據類型 | 包裝類 |
|---|---|
| byte | Byte |
| boolean | Boolean |
| short | Short |
| char | Character |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
在這八個類名中,除了Integer和Character類以外,其它六個類的類名和基本數據類型一致,只是類名的第一個字母大寫而已。
Number類是抽象類,因此它的抽象方法,所有子類都需要提供實現。Number類提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味著所有的“數字型”包裝類都可以互相轉型。
下面我們通過一個簡單的示例認識一下包裝類。
【示例】初識包裝類
public class WrapperClassTest {
public static void main(String[ ] args) {
Integer i = new Integer(10); //從java9開始被廢棄
Integer j = Integer.valueOf(50); //官方推薦
}
}
?
包裝類的用途
對于包裝類來說,這些類的用途主要包含兩種:
-
作為和基本數據類型對應的類型存在,方便涉及到對象的操作,如
Object[ ]、集合等的操作。 -
包含每種基本數據類型的相關屬性如最大值、最小值等,以及相關的操作方法(這些操作方法的作用是在基本數據類型、包裝類對象、字符串之間提供相互之間的轉化!)。
【示例】包裝類的使用
public class Test {
/** 測試Integer的用法,其他包裝類與Integer類似 */
void testInteger() {
// 基本類型轉化成Integer對象
Integer int1 = new Integer(10); //已廢棄,不推薦使用
Integer int2 = Integer.valueOf(20); // 官方推薦這種寫法
// Integer對象轉化成int
int a = int1.intValue();
// 字符串轉化成Integer對象
Integer int3 = Integer.parseInt("334");
Integer int4 = new Integer("999");
// Integer對象轉化成字符串
String str1 = int3.toString();
// 一些常見int類型相關的常量
System.out.println("int能表示的最大整數:" + Integer.MAX_VALUE);
}
public static void main(String[ ] args) {
Test test = new Test();
test.testInteger();
}
}
?執行結果如圖所示:

自動裝箱和拆箱
自動裝箱(autoboxing)和拆箱(unboxing):將基本數據類型和包裝類自動轉換。
自動裝箱:
基本類型的數據處于需要對象的環境中時,會自動轉為“對象”。
我們以Integer為例:
Integer i = 5
編譯器會自動轉成:Integer i = Integer.valueOf(5),這就是Java的自動裝箱。
自動拆箱:
每當需要一個值時,對象會自動轉成基本數據類型,沒必要再去顯式調用intValue()、doubleValue()等轉型方法。
Integer i = Integer.valueOf(5);
int j = i;
編譯器會自動轉成:int j = i.intValue();
這樣的過程就是自動拆箱。
自動裝箱/拆箱的本質是:
自動裝箱與拆箱的功能是編譯器來幫忙,編譯器在編譯時依據您所編寫的語法,決定是否進行裝箱或拆箱動作。
【示例】自動裝箱
Integer i = 100;//自動裝箱
//相當于編譯器自動為您作以下的語法編譯:
Integer i = Integer.valueOf(100);//調用的是valueOf(100),而不是new Integer(100)
?【示例】自動拆箱
Integer i = 100;
int j = i;//自動拆箱
//相當于編譯器自動為您作以下的語法編譯:
int j = i.intValue();
?自動裝箱與拆箱的功能是所謂的“編譯器蜜糖(Compiler Sugar)”,雖然使用這個功能很方便,但在程序運行階段您得了解Java的語義。如下所示的程序是可以通過編譯的:
【示例】包裝類空指針異常問題
public class Test1 {
public static void main(String[ ] args) {
Integer i = null;
int j = i;
}
}
執行結果如圖所示:

運行結果之所以會出現空指針異常,是因為如上代碼相當于:
public class Test1 {
public static void main(String[ ] args) {
/*示例8-5的代碼在編譯時期是合法的,但是在運行時期會有錯誤因為其相當于下面兩行代碼*/
Integer i = null;
int j = i.intValue();
}
}
?
包裝類的緩存問題
整型、char類型所對應的包裝類,在自動裝箱時,對于-128~127之間的值會進行緩存處理,其目的是提高效率。
緩存原理為:如果數據在-128~127這個區間,那么在類加載時就已經為該區間的每個數值創建了對象,并將這256個對象存放到一個名為cache的數組中。每當自動裝箱過程發生時(或者手動調用valueOf()時),就會先判斷數據是否在該區間,如果在則直接獲取數組中對應的包裝類對象的引用,如果不在該區間,則會通過new調用包裝類的構造方法來創建對象。
下面我們以Integer類為例,看一看Java為我們提供的源碼,加深對緩存技術的理解,如示例所示。
【示例】Integer類相關源碼
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
-
為Integer類的一個靜態內部類,僅供Integer類使用。
-
一般情況下
IntegerCache.low為-128,IntegerCache.high為127,
IntegerCache.cache為內部類的一個靜態屬性,如示例所示。
【示例】IntegerCache類相關源碼
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[ ];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
?
?
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
?由上面的源碼我們可以看到,靜態代碼塊的目的就是初始化數組cache的,這個過程會在類加載時完成。
下面我們做一下代碼測試,如示例所示。
【示例8-9】包裝類的緩存測試
public class Test3 {
public static void main(String[ ] args) {
Integer in1 = -123;
Integer in2 = -123;
System.out.println(in1 == in2);//true 因為123在緩存范圍內
System.out.println(in1.equals(in2));//true
Integer in3 = 1234;
Integer in4 = 1234;
System.out.println(in3 == in4);//false 因為1234不在緩存范圍內
System.out.println(in3.equals(in4));//true
}
}
總結
1?? 自動裝箱調用的是valueOf()方法,而不是new Integer()方法。
2?? 自動拆箱調用的xxxValue()方法。
3?? 包裝類在自動裝箱時為了提高效率,對于-128~127之間的值會進行緩存處理。超過范圍后,對象之間不能再使用==進行數值的比較,而是使用equals方法。
浙公網安備 33010602011771號