包裝類的緩存及自動裝箱、拆箱

我們知道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

拓展

  1. 為什么是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。


數(shù)組最大長度
  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;
  1. 疑問: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ù)驗證:


printf方法形參、入?yún)?/div>
  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。(破獲大案/笑哭)


int轉(zhuǎn)Object自動裝箱

多態(tài)調(diào)用Integer的toString()方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容