我們知道Java有八個基礎(chǔ)類型,同時為每個基礎(chǔ)類型提供了對應(yīng)的包裝類型,對應(yīng)關(guān)系如下:
| 基礎(chǔ)類型 | 包裝類型 |
|---|---|
| byte | Integer |
| char | Character |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| boolean | Boolean |
什么是自動裝箱/拆箱?
自動裝箱:將基本類型的變量賦值給對應(yīng)的包裝類
自動拆箱:將包裝類對象直接賦值給對應(yīng)的基礎(chǔ)類型變量
什么情況下會觸發(fā)自動裝箱?
- 基本類型賦值給包裝類型時會觸發(fā)自動裝箱,調(diào)用包裝類型的valueOf()方法。
Integer i = 100;
// 上面的代碼相當于:
Integer i = Integer.valueOf(100);
什么情況下會觸發(fā)自動拆箱?
- 當包裝類參與運算時觸發(fā)自動拆箱,調(diào)用對應(yīng)的xxxValue()方法。
- 包裝類與基本類型比較、或賦值給基本類型時,觸發(fā)自動拆箱
Integer i = 100;
i ++; // i參與運算,自動拆箱
int j = i;// i賦值給基本類型,自動拆箱
解釋了自動裝箱、拆箱,我們來看一道經(jīng)典面試題:
Integer i01 = 59;// 相當于 Integer.valueOf(59);
int i02 = 59;
Integer i03 = Integer.valueOf(59);
Integer i04 = new Integer(59);
System.out.println(i01 == i02);
System.out.println(i01 == i03);
System.out.println(i03 == i04);
System.out.println(i02 == i04);
我們一起分析一下:
i01 == i02,因為i02為基本類型,i01自動拆箱,應(yīng)該返回true;
i01和i03為兩個對象,應(yīng)該返回false;(這個結(jié)論是錯誤的,后面會解釋)
i03、i04為兩個不同對象,應(yīng)該返回false;
i02 == i04,i04自動拆箱,應(yīng)該返回true;
但實際運行結(jié)果是:
true
true
false
true
為什么i01 == i03 會返回true呢?我們知道引用類型“==”比較的是對象的地址即兩者是不是同一個對象,這兩個為什么會是同一個對象?我們來看一下Integer源碼(JDK8)中valueOf(int i)方法是怎么實現(xiàn)的:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;// 緩存池最小值,默認-128
static final int high;// 緩存池最大值,默認127
static final Integer cache[];// 緩存數(shù)組
static {
int h = 127;
//可通過修改參數(shù)"java.lang.Integer.IntegerCache.high"來設(shè)置緩存池的最大值
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
// MAX_VALUE = 0x7fffffff,即(2^32)-1
// 取較小值:為什么Integer.MAX_VALUE - (-low) - 1?
h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
} catch (NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
// 如果不能轉(zhuǎn)換成int類型,就忽略它
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for (int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// 斷言有什么作用?
assert Integer.IntegerCache.high >= 127;
}
private IntegerCache() {}
}
valueOf(int i)方法先判斷了變量i的值是否在[IntegerCache.low,IntegerCache.high]范圍內(nèi):
如果在范圍內(nèi),則返回的IntegerCache.cache[i + (-IntegerCache.low)];
不在范圍內(nèi)則返回 new Integer(i);
通過IntegerCache內(nèi)部類的源碼我們得知,默認情況下在[-128,127]會被直接放到緩存中,創(chuàng)建在這個范圍內(nèi)的包裝類型時會直接返回創(chuàng)建好的對象,并不會重新創(chuàng)建對象。這就是為什么i01 == i03了。
那為什么i04!=i03呢?i04直接調(diào)用了構(gòu)造方法,返回的新建的對象,并不是從緩存中得到的,所以i04和i03不相等。
private final int value;
public Integer(int value) {
this.value = value;
}
包裝類的緩存
通過查看包裝類源碼,我們發(fā)現(xiàn)Java為了在自動裝箱時避免每次去創(chuàng)建包裝類型,采用了緩存技術(shù)。即在類加載時,初始化一定數(shù)量的常用數(shù)據(jù)對象(即常說的熱點數(shù)據(jù)),放入緩存中,等到使用時直接命中緩存,減少資源浪費。Java提供的8種基本數(shù)據(jù)類型,除了float和double外,都采用了緩存機制。其實原因也很簡單,因為浮點型數(shù)據(jù),熱點數(shù)據(jù)并不好確定,故并未采用。
各包裝類型緩存范圍如下:
| 包裝類型 | 緩存值 |
|---|---|
| Character | 0~127 |
| Byte,Short,Integer,Long | -128 到 127 |
| Boolean | true,false |
拓展
- 為什么是Integer.MAX_VALUE - (-low) - 1?
// MAX_VALUE = 0x7fffffff,即(2^32)-1
// 取較小值:為什么Integer.MAX_VALUE - (-low) - 1?
h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
因為緩存的下限是-128,程序中已經(jīng)寫死,不能用過配置修改,緩存采用數(shù)組來存放需要緩存的Integer對象,java中數(shù)組最大長度 = int的最大值,int占用32位,最大值為(2^32)-1即2147483647,所以 h - low <= MAX_VALUE - 1,由此得出h的最大值為Integer.MAX_VALUE - (-low) - 1。

- assert斷言
assert格式:
1)assert [boolean 表達式]
如果[boolean表達式]為true,則程序繼續(xù)執(zhí)行。
如果為false,則程序拋出AssertionError,并終止執(zhí)行。
2)assert[boolean 表達式 : 錯誤表達式 (日志)]
如果[boolean表達式]為true,則程序繼續(xù)執(zhí)行。
如果為false,則程序拋出java.lang.AssertionError,輸出[錯誤信息]。
// 斷言有什么作用?
// 由assert的語義可知,此處保證IntegerCache.high最小值為127。
assert Integer.IntegerCache.high >= 127;
-
疑問:Integer緩存的bug?
在網(wǎng)上看到一篇文章Integer中IntegerCache使用及分析,文章末尾提到了這段代碼:
public static void main(String[] args) throws Exception {
Class<?> clazz = Integer.class.getDeclaredClasses()[0];
Field field = clazz.getDeclaredField("cache");
field.setAccessible(true);
Integer[] cache = (Integer[]) field.get(clazz);
cache[132] = cache[133];
int a = 2;
int b = a + a;
System.out.printf("%d+%d=%d", a, a, b);
System.out.println();
System.out.println(a + "+" + a + "=" + b);
}
查看printf方法源碼發(fā)現(xiàn)方法形參為Object對象類型,而入?yún)閕nt基本類型,猜測發(fā)生自動裝箱,下面我們寫一段代碼繼續(xù)驗證:

public static void main(String[] args) {
try {
int a = 2;
int b = 4;
Class<?> clazz = Integer.class.getDeclaredClasses()[0];
Field field = clazz.getDeclaredField("cache");
field.setAccessible(true);
Integer[] cache = (Integer[]) field.get(clazz);
cache[132] = cache[133];
Object obj1 = a;
Object obj2 = b;
System.out.println(obj1);
System.out.println(obj2);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
通過斷點及輸出結(jié)果我們發(fā)現(xiàn)int轉(zhuǎn)為Object類型時發(fā)生了自動裝箱,而4在[-128,127]范圍內(nèi),多態(tài)情況下Object.toString調(diào)用了Integer的toString方法,輸出了緩存中的5。(破獲大案/笑哭)


【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- 概述 JAVA中的自動裝箱指的是把基本類型的值轉(zhuǎn)換為對應(yīng)的包裝類對象,自動拆箱則相反。 JAVA中的基本類型: b...
- 關(guān)于Java自動裝箱和拆箱 基本數(shù)據(jù)(Primitive)類型的自動裝箱(autoboxing)、拆箱(unbox...
- 1、 什么是包裝類 包裝類就是Java基本數(shù)據(jù)類型的對象表示形式。其中包括基本數(shù)據(jù)類型byte, char, sh...