Integer用==比較127相等128不相等的原因

前言

這個幾乎是Java 5引入自動裝箱和自動拆箱后,很多人都會遇到(而且不止一次),而又完全摸不著頭腦的坑。雖然已有很多文章分析了原因,但鑒于我這次還差點坑了同學,還是紀錄下來長點記性。

問題描述


例一

來個簡單點的例子

public static void main(String[] args) {
    for (int i = 0; i < 150; i++) {
        Integer a = i;
        Integer b = i;
        System.out.println(i + " " + (a == b));
    }
}

i取值從0到150,每次循環a與b的數值均相等,輸出a == b。運行結果:

0 true
1 true
2 true
3 true
...
126 true
127 true
128 false
129 false
130 false

從128開始ab就不再相等了。

這個例子還容易看出來涉及到int的自動裝箱和自動拆箱,下面來個不太容易看出來的。

例二

public static void main(String[] args) {
    Map<Integer, Integer> mapA = new HashMap<>();
    Map<Integer, Integer> mapB = new HashMap<>();
    for (int i = 0; i < 150; i++) {
        mapA.put(i, i);
        mapB.put(i, i);
    }
    for (int i = 0; i < 150; i++) {
        System.out.println(i + " " + (mapA.get(i) == mapB.get(i)));
    }
}

i取值從0150mapAmapB均存儲(i, i)數值對,輸出mapA的值與mapB的值的比較結果。運行結果

0 true
1 true
2 true
3 true
...
126 true
127 true
128 false
129 false
130 false
...

為什么兩個例子都是從0到127均顯示兩個變量相等,而從128開始不相等?

原因分析


自動裝箱

首先回顧一下自動裝箱。對于下面這行代碼

Integer a = 1;

變量aInteger類型,而1int類型,且Integerint之間并無繼承關系,按照Java的一般處理方法,這行代碼應該報錯。

但因為自動裝箱機制的存在,在為Integer類型的變量賦int類型值時,Java會自動將int類型轉換為Integer類型,即

Integer a = Integer.valueOf(1);

valueOf()方法返回一個Integer類型值,并將其賦值給變量a。這就是int的自動裝箱。

是同一個對象嗎?

再看最開始的例子:

public static void main(String[] args) {
    for (int i = 0; i < 150; i++) {
        Integer a = i;
        Integer b = i;
        System.out.println(i + " " + (a == b));
    }
}

每次循環時,Integer a = iInteger b = i都會觸發自動裝箱,而自動裝箱會將int轉換Integer類型值并返回;我們知道Java中兩個new出來的對象因為時不同的實例,無論如何==都會返回fasle。比如

new Integer(1) == new Integer(1);

就會返回false。

那么例子中Integer a = iInteger b = i自動裝箱產生的變量ab就不應該時同一個對象了,那么==的結果應該時false。128以上為false容易理解,但為何0到127時返回true了呢?==返回true的唯一情況是比較的兩個對象為同一個對象,那不妨把例子中ab的內存地址都打印出來看看:

for(int i=0;i<150;i++){
    Integer a=i;
    Integer b=i;
    System.out.println(a+" "+b+" "+System.identityHashCode(a)+" "+System.identityHashCode(b));
}

identityHashCode()方法可以理解為輸出對應變量的內存地址,輸出為:

0 0 762119098 762119098
1 1 1278349992 1278349992
2 2 1801910956 1801910956
3 3 1468253089 1468253089
...
126 126 1605164995 1605164995
127 127 1318497351 1318497351
128 128 101224864 479240824
129 129 1373088356 636728630
130 130 587071409 1369296745
...

竟然從0到127不同時候自動裝箱得到的是同一個對象!從128開始才是正常情況。

看看源碼

“從0到127不同時候自動裝箱得到的是同一個對象”就只能有一種解釋:自動裝箱并不一定new出新的對象。

既然自動裝箱涉及到的方法是Integer.valueOf(),不妨看看其源代碼:

/**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @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);
    }

其注釋里就直接說明了-128到127之間的值都是直接從緩存中取出的。看看是怎么實現的:如果int型參數iIntegerCache.lowIntegerCache.high范圍內,則直接由IntegerCache返回;否則new一個新的對象返回。似乎IntegerCache.low就是-128,IntegerCache.high就是127了。
看看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() {}
}

果然在其static塊中就一次性生成了-128到127直接的Integer類型變量存儲在cache[]中,對于-128到127之間的int類型,返回的都是同一個Integer類型對象。

這下真相大白了,整個工作過程就是:Integer.class在裝載(Java虛擬機啟動)時,其內部類型IntegerCache的static塊即開始執行,實例化并暫存數值在-128到127之間的Integer類型對象。當自動裝箱int型值在-128到127之間時,即直接返回IntegerCache中暫存的Integer類型對象。

為什么Java這么設計?我想是出于效率考慮,因為自動裝箱經常遇到,尤其是小數值的自動裝箱;而如果每次自動裝箱都觸發new,在堆中分配內存,就顯得太慢了;所以不如預先將那些常用的值提前生成好,自動裝箱時直接拿出來返回。哪些值是常用的?就是-128到127了。

解決方法

既然我們的目的是比較數值是否相等,而非判斷是否為同一對象;而自動裝箱又不能保證同一數值的Integer一定是同一對象或一定不是同一對象,那么就不要用==,直接用equals()好了。實際上,Integer重寫了equals()方法,直接比較對象的數值是否相等。

for (int i = 0; i < 150; i++) {
    Integer a = i;
    Integer b = i;
    System.out.println(i + " " + (a.equals(b)));
}

這樣返回值就全都是true了。

備注


不僅int,Java中的另外7中基本類型都可以自動裝箱和自動拆箱,其中也有用到緩存。見下表:

基本類型 裝箱類型 取值范圍 是否緩存 緩存范圍
byte Byte -128 ~ 127 -128 ~ 127
short Short -2^15 ~ (2^15 - 1) -128 ~ 127
int Integer -2^31 ~ (2^31 - 1) -128 ~ 127
long Long -2^63 ~ (2^63 - 1) -128 ~ 127
float Float -- --
double Double -- --
boolean Boolean true, false true, false
char Character \u0000 ~ \uffff \u0000 ~ \u007f

本文源自Java: Integer用==比較時127相等128不相等的原因

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容

  • 一、Java 簡介 Java是由Sun Microsystems公司于1995年5月推出的Java面向對象程序設計...
    子非魚_t_閱讀 4,236評論 1 44
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,722評論 0 11
  • 常去的一家小店,店內的餐食算不上美味,但也勉強果腹。裝潢是清新文藝的風格,原木桌椅,精心挑選的裝飾小物,正...
    梅子Sophy閱讀 158評論 0 0
  • 南紅珠串是南紅收藏成品中最為普遍和超值的一種,市場對于優質南紅珠串的需求也是比較大的。大家在鑒別南紅珠串優劣時,會...
    五柳文玩華掌柜閱讀 1,027評論 0 0