你所不知道的Java之Integer

以下內(nèi)容為作者辛苦原創(chuàng)蜒简,版權(quán)歸作者所有瘸羡,如轉(zhuǎn)載演繹請(qǐng)?jiān)凇肮庾儭蔽⑿殴娞?hào)留言申請(qǐng),轉(zhuǎn)載文章請(qǐng)?jiān)陂_始處顯著標(biāo)明出處搓茬。

實(shí)參形參

前些天看到朋友圈分享了一片文章《Java函數(shù)的傳參機(jī)制——你真的了解嗎犹赖?》

有些觸發(fā),之前也研究過(guò)Java的Integer卷仑,所以寫下本文峻村,希望對(duì)你有所幫助。

交換

首先來(lái)看一個(gè)示例锡凝。

請(qǐng)用Java完成swap函數(shù)粘昨,交換兩個(gè)整數(shù)類型的值。

public static void test() throws Exception {
    Integer a = 1, b = 2;
    swap(a, b);
    System.out.println("a=" + a + ", b=" + b);
}

static void swap(Integer a, Integer b){
   // 需要實(shí)現(xiàn)的部分
}

第一次
如果你不了解Java對(duì)象在內(nèi)存中的分配方式,以及方法傳遞參數(shù)的形式张肾,你有可能會(huì)寫出以下代碼芭析。

public static void swapOne(Integer a, Integer b) throws Exception {
    Integer aTempValue = a;
    a = b;
    b = aTempValue;
}

運(yùn)行的結(jié)果顯示a和b兩個(gè)值并沒(méi)有交換。
那么讓我們來(lái)看一下上述程序運(yùn)行時(shí)吞瞪,Java對(duì)象在內(nèi)存中的分配方式:


對(duì)象地址分配

由此可以看到馁启,在兩個(gè)方法的局部變量表中分別持有的是對(duì)a、b兩個(gè)對(duì)象實(shí)際數(shù)據(jù)地址的引用芍秆。
上面實(shí)現(xiàn)的swap函數(shù)惯疙,僅僅交換了swap函數(shù)里局部變量a和局部變量b的引用,并沒(méi)有交換JVM堆中的實(shí)際數(shù)據(jù)妖啥。
所以main函數(shù)中的a霉颠、b引用的數(shù)據(jù)沒(méi)有發(fā)生交換,所以main函數(shù)中局部變量的a荆虱、b并不會(huì)發(fā)生變化蒿偎。

那么要交換main函數(shù)中的數(shù)據(jù)要如何操作呢?

第二次
根據(jù)上面的實(shí)踐克伊,可以考慮交換a和b在JVM堆上的數(shù)據(jù)值酥郭?
簡(jiǎn)單了解一下Integer這個(gè)對(duì)象,它里面只有一個(gè)對(duì)象級(jí)int類型的value用以表示該對(duì)象的值愿吹。
所以我們使用反射來(lái)修改該值不从,代碼如下:

public static void swapTwo(Integer a1, Integer b1) throws Exception {
    Field valueField = Integer.class.getDeclaredField("value");
    valueField.setAccessible(true);
    int tempAValue = valueField.getInt(a1);
    valueField.setInt(a1, b1.intValue());
    valueField.setInt(b1, tempAValue);
}

運(yùn)行結(jié)果,符合預(yù)期犁跪。

驚喜

上面的程序運(yùn)行成后椿息,如果我在聲明一個(gè)Integer c = 1, d = 2;會(huì)有什么結(jié)果

示例程序如下:


public static void swapTwo(Integer a1, Integer b1) throws Exception {
    Field valueField = Integer.class.getDeclaredField("value");
    valueField.setAccessible(true);
    int tempAValue = valueField.getInt(a1);
    valueField.setInt(a1, b1.intValue());
    valueField.setInt(b1, tempAValue);
}

public static void testThree() throws Exception {
    Integer a = 1, b = 2;
    swapTwo(a, b);
    System.out.println("a=" + a + "; b=" + b);
    Integer c = 1, d = 2;
    System.out.println("c=" + c + "; d=" + d);
}

輸出的結(jié)果如下:

a=2; b=1
c=2; d=1

驚喜不驚喜!意外不意外坷衍!刺激不刺激寝优!

驚喜不驚喜

深入

究竟發(fā)生了什么?讓我們來(lái)看一下反編譯后的代碼:

作者使用IDE工具枫耳,直接反編譯了這個(gè).class文件

public static void testThree() throws Exception {
    Integer a = Integer.valueOf(1);
    Integer b = Integer.valueOf(2);
    swapTwo(a, b);
    System.out.println("a=" + a + "; b=" + b);
    Integer c = Integer.valueOf(1);
    Integer d = Integer.valueOf(2);
    System.out.println("c=" + c + "; d=" + d);
}

在Java對(duì)原始類型int自動(dòng)裝箱到Integer類型的過(guò)程中使用了Integer.valueOf(int)這個(gè)方法了乏矾。
肯定是這個(gè)方法在內(nèi)部封裝了一些操作,使得我們修改了Integer.value后迁杨,產(chǎn)生了全局影響钻心。
所有這涉及該部分的代碼一次性粘完(PS:不拖拉的作者是個(gè)好碼農(nóng)):

public class Integer{
    /**
     * @since  1.5
     */
    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;
        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() {}
    }
    
}

如上所示Integer內(nèi)部有一個(gè)私有靜態(tài)類IntegerCache,該類靜態(tài)初始化了一個(gè)包含了Integer.IntegerCache.lowjava.lang.Integer.IntegerCache.high的Integer數(shù)組铅协。
其中java.lang.Integer.IntegerCache.high的取值范圍在[127~Integer.MAX_VALUE - (-low) -1]之間捷沸。
在該區(qū)間內(nèi)所有的Integer.valueOf(int)函數(shù)返回的對(duì)象,是根據(jù)int值計(jì)算的偏移量狐史,從數(shù)組Integer.IntegerCache.cache中獲取痒给,對(duì)象是同一個(gè)说墨,不會(huì)新建對(duì)象。

所以當(dāng)我們修改了Integer.valueOf(1)value后苍柏,所有Integer.IntegerCache.cache[ 1 - IntegerCache.low ]的返回值都會(huì)變更尼斧。

我相信你們的智商應(yīng)該理解了,如果不理解請(qǐng)?jiān)谠u(píng)論區(qū)call 10086序仙。

好了突颊,那么不在[IntegerCache.low~IntegerCache.high)的部分呢?
很顯然潘悼,它們是幸運(yùn)的,沒(méi)有被IntegerCache緩存到爬橡,法外之民治唤,每次它們的到來(lái),都會(huì)new一邊糙申,在JVM上分配一塊土(內(nèi))地(存)宾添。

遐想

如果我把轉(zhuǎn)換的參數(shù)換成類型換成int呢?

public static void testOne() throws Exception {
    int a = 1, b = 2;
    swapOne(a, b);
    System.out.println("a=" + a + ", b=" + b);
}

static void swapOne(int a, int b){
   // 需要實(shí)現(xiàn)的部分
}

以作者目前的功力柜裸,無(wú)解缕陕。高手可以公眾號(hào)留言,萬(wàn)分感謝疙挺!
至此swap部分已經(jīng)講完了扛邑。

1 + 1

首先讓我們來(lái)看一下代碼:

public static void testOne() {
    int one = 1;
    int two = one + one;
    System.out.printf("Two=%d", two);
}

請(qǐng)問(wèn)輸出是什么?
如果你肯定的說(shuō)是2铐然,那么你上面是白學(xué)了蔬崩,請(qǐng)直接撥打95169
我可以肯定的告訴你搀暑,它可以是[Integer.MIN_VALUE~Integer.MAX_VALUE]區(qū)間的任意一個(gè)值沥阳。

驚喜不驚喜!意外不意外自点!刺激不刺激桐罕!

驚喜不驚喜

讓我們?cè)贁](捋)一(一)串(遍)燒(代)烤(碼)。

作者使用IDE工具桂敛,直接反編譯了這個(gè).class文件

public static void testOne() {
    int one = 1;
    int two = one + one;
    System.out.printf("Two=%d", two);
}

這里的變量two竟然沒(méi)有調(diào)用Integer.valueOf(int)功炮,跟想象的不太一樣,我懷疑這是IDE的鍋埠啃。
所以果斷查看編譯后的字節(jié)碼死宣。以下為摘錄的部分字節(jié)碼:

LDC "Two=%d"
ICONST_1
ANEWARRAY java/lang/Object
DUP
ICONST_0
ILOAD 2
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
AASTORE
INVOKEVIRTUAL java/io/PrintStream.printf (Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
POP

可以看出確實(shí)是IDE的鍋,這里不僅調(diào)用了一次Integer.valueOf(int)碴开,而且還創(chuàng)建一個(gè)Object的數(shù)組毅该。
完整的Java代碼應(yīng)該是如下所示:

public static void testOne() {
    int one = 1;
    int two = one + one;
    Object[] params = { Integer.valueOf(two) };
    System.out.printf("Two=%d", params);
}

所以只要在方法調(diào)用前修改Integer.IntegerCache.cache[2+128]的值就可以了博秫,所以在類的靜態(tài)初始化部分加些代碼。

public class OnePlusOne {
    static {
        try {
            Class<?> cacheClazz = Class.forName("java.lang.Integer$IntegerCache");
            Field cacheField = cacheClazz.getDeclaredField("cache");
            cacheField.setAccessible(true);
            Integer[] cache = (Integer[]) cacheField.get(null);
            //這里修改為 1 + 1 = 3            
            cache[2 + 128] = new Integer(3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testOne() {
        int one = 1;
        int two = one + one;
        System.out.printf("Two=%d", two);
    }
}

two == 2 眶掌?

在修改完Integer.IntegerCache.cache[2 + 128]的值后挡育,變量two還等于2么?

public static void testTwo() {
    int one = 1;
    int two = one + one;
    System.out.println(two == 2);
    System.out.println(Integer.valueOf(two) == 2);
}

上述代碼輸出如下

true
false

因?yàn)?code>two == 2不涉及到Integer裝箱的轉(zhuǎn)換朴爬,還是原始類型的比較即寒,所以原始類型的2永遠(yuǎn)等于2
Integer.valueOf(two)==2的真實(shí)形式是Integer.valueOf(two).intValue == 2召噩,即3==2母赵,所以是false。

這里可以看到如果拿一個(gè)值為null的Integer變量和一個(gè)int變量用雙等號(hào)比較具滴,會(huì)拋出NullPointException凹嘲。

這里的方法如果換成System.out.println("Two=" + two)的形式會(huì)有怎樣的輸出?你可以嘗試一下构韵。

后記

XCache

是否有Cache 最小值 最大值
Boolean 無(wú) -- --
Byte ByteCache -128 127(固定)
Short ShortCache -128 127(固定)
Character CharacterCache 0 127(固定)
Integer IntegerCache -128 java.lang.Integer.IntegerCache.high
Long LongCache -128 127(固定)
Float 無(wú) -- --
Double 無(wú) -- --

java.lang.Integer.IntegerCache.high

看了IntegerCache類獲取high的方法sun.misc.VM.getSavedProperty周蹭,可能大家會(huì)有以下疑問(wèn),我們不拖沓疲恢,采用一個(gè)問(wèn)題一解答的方式凶朗。

1. 這個(gè)值如何如何傳遞到JVM中?

和系統(tǒng)屬性一樣在JVM啟動(dòng)時(shí)显拳,通過(guò)設(shè)置-Djava.lang.Integer.IntegerCache.high=xxx傳遞進(jìn)來(lái)棚愤。

2. 這個(gè)方法和System.getProperty有什么區(qū)別?

為了將JVM系統(tǒng)所需要的參數(shù)和用戶使用的參數(shù)區(qū)別開萎攒,
java.lang.System.initializeSystemClass在啟動(dòng)時(shí)遇八,會(huì)將啟動(dòng)參數(shù)保存在兩個(gè)地方:
2.1 sun.misc.VM.savedProps中保存全部JVM接收的系統(tǒng)參數(shù)。
JVM會(huì)在啟動(dòng)時(shí)耍休,調(diào)用java.lang.System.initializeSystemClass方法刃永,初始化該屬性。
同時(shí)也會(huì)調(diào)用sun.misc.VM.saveAndRemoveProperties方法羊精,從java.lang.System.props中刪除以下屬性:

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag
    以上羅列的屬性都是JVM啟動(dòng)需要設(shè)置的系統(tǒng)參數(shù)斯够,所以為了安全考慮和隔離角度考慮,將其從用戶可訪問(wèn)的System.props分開喧锦。

2.2 java.lang.System.props中保存除了以下JVM啟動(dòng)需要的參數(shù)外的其他參數(shù)读规。

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag

PS:作者使用的JDK 1.8.0_91

Java 9的IntegerCache

幻想一下,如果以上淘氣的玩法出現(xiàn)在第三方的依賴包中燃少,絕對(duì)有一批程序員會(huì)瘋掉(請(qǐng)不要嘗試這么惡劣的玩法束亏,后果很嚴(yán)重)。

慶幸的是Java 9對(duì)此進(jìn)行了限制阵具“椋可以在相應(yīng)的module中編寫module-info.java文件定铜,限制了使用反射來(lái)訪問(wèn)成員等,按照需要聲明后怕敬,代碼只能訪問(wèn)字段揣炕、方法和其他用反射能訪問(wèn)的信息,只有當(dāng)類在相同的模塊中东跪,或者模塊打開了包用于反射方式訪問(wèn)畸陡。詳細(xì)內(nèi)容可參考一下文章:
在 Java 9 里對(duì) IntegerCache 進(jìn)行修改?

感謝Lydia和飛鳥的寶貴建議和辛苦校對(duì)虽填。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丁恭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子卤唉,更是在濱河造成了極大的恐慌涩惑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桑驱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡跛蛋,警方通過(guò)查閱死者的電腦和手機(jī)熬的,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赊级,“玉大人押框,你說(shuō)我怎么就攤上這事±硌罚” “怎么了橡伞?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)晋被。 經(jīng)常有香客問(wèn)我兑徘,道長(zhǎng),這世上最難降的妖魔是什么羡洛? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任挂脑,我火速辦了婚禮,結(jié)果婚禮上欲侮,老公的妹妹穿的比我還像新娘崭闲。我一直安慰自己,他們只是感情好威蕉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布刁俭。 她就那樣靜靜地躺著,像睡著了一般韧涨。 火紅的嫁衣襯著肌膚如雪牍戚。 梳的紋絲不亂的頭發(fā)上侮繁,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音翘魄,去河邊找鬼鼎天。 笑死,一個(gè)胖子當(dāng)著我的面吹牛暑竟,可吹牛的內(nèi)容都是我干的斋射。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼但荤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼罗岖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腹躁,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桑包,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纺非,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哑了,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年烧颖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弱左。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炕淮,死狀恐怖拆火,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涂圆,我是刑警寧澤们镜,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站润歉,受9級(jí)特大地震影響模狭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卡辰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一胞皱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧九妈,春花似錦反砌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至晶疼,卻和暖如春酒贬,著一層夾襖步出監(jiān)牢的瞬間又憨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工锭吨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蠢莺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓零如,卻偏偏與公主長(zhǎng)得像躏将,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子考蕾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容