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

前言

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

問(wèn)題描述


例一

來(lái)個(gè)簡(jiǎn)單點(diǎn)的例子

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,每次循環(huán)a與b的數(shù)值均相等,輸出a == b。運(yùn)行結(jié)果:

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

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

這個(gè)例子還容易看出來(lái)涉及到int的自動(dòng)裝箱和自動(dòng)拆箱,下面來(lái)個(gè)不太容易看出來(lái)的。

例二

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取值從0150,mapAmapB均存儲(chǔ)(i, i)數(shù)值對(duì),輸出mapA的值與mapB的值的比較結(jié)果。運(yùn)行結(jié)果

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

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

原因分析


自動(dòng)裝箱

首先回顧一下自動(dòng)裝箱。對(duì)于下面這行代碼

Integer a = 1;

變量aInteger類型,而1int類型,且Integerint之間并無(wú)繼承關(guān)系,按照J(rèn)ava的一般處理方法,這行代碼應(yīng)該報(bào)錯(cuò)。

但因?yàn)樽詣?dòng)裝箱機(jī)制的存在,在為Integer類型的變量賦int類型值時(shí),Java會(huì)自動(dòng)將int類型轉(zhuǎn)換為Integer類型,即

Integer a = Integer.valueOf(1);

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

是同一個(gè)對(duì)象嗎?

再看最開始的例子:

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));
    }
}

每次循環(huán)時(shí),Integer a = iInteger b = i都會(huì)觸發(fā)自動(dòng)裝箱,而自動(dòng)裝箱會(huì)將int轉(zhuǎn)換Integer類型值并返回;我們知道Java中兩個(gè)new出來(lái)的對(duì)象因?yàn)闀r(shí)不同的實(shí)例,無(wú)論如何==都會(huì)返回fasle。比如

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

就會(huì)返回false。

那么例子中Integer a = iInteger b = i自動(dòng)裝箱產(chǎn)生的變量ab就不應(yīng)該時(shí)同一個(gè)對(duì)象了,那么==的結(jié)果應(yīng)該時(shí)false。128以上為false容易理解,但為何0到127時(shí)返回true了呢?==返回true的唯一情況是比較的兩個(gè)對(duì)象為同一個(gè)對(duì)象,那不妨把例子中ab的內(nèi)存地址都打印出來(lái)看看:

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()方法可以理解為輸出對(duì)應(yīng)變量的內(nèi)存地址,輸出為:

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不同時(shí)候自動(dòng)裝箱得到的是同一個(gè)對(duì)象!從128開始才是正常情況。

看看源碼

“從0到127不同時(shí)候自動(dòng)裝箱得到的是同一個(gè)對(duì)象”就只能有一種解釋:自動(dòng)裝箱并不一定new出新的對(duì)象。

既然自動(dòng)裝箱涉及到的方法是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);
    }

其注釋里就直接說(shuō)明了-128到127之間的值都是直接從緩存中取出的??纯词窃趺磳?shí)現(xiàn)的:如果int型參數(shù)iIntegerCache.lowIntegerCache.high范圍內(nèi),則直接由IntegerCache返回;否則new一個(gè)新的對(duì)象返回。似乎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類型變量存儲(chǔ)在cache[]中,對(duì)于-128到127之間的int類型,返回的都是同一個(gè)Integer類型對(duì)象。

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

為什么Java這么設(shè)計(jì)?我想是出于效率考慮,因?yàn)樽詣?dòng)裝箱經(jīng)常遇到,尤其是小數(shù)值的自動(dòng)裝箱;而如果每次自動(dòng)裝箱都觸發(fā)new,在堆中分配內(nèi)存,就顯得太慢了;所以不如預(yù)先將那些常用的值提前生成好,自動(dòng)裝箱時(shí)直接拿出來(lái)返回。哪些值是常用的?就是-128到127了。

解決方法

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

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

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

備注


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

基本類型 裝箱類型 取值范圍 是否緩存 緩存范圍
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用==比較時(shí)127相等128不相等的原因

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

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

  • 一、Java 簡(jiǎn)介 Java是由Sun Microsystems公司于1995年5月推出的Java面向?qū)ο蟪绦蛟O(shè)計(jì)...
    子非魚_t_閱讀 4,538評(píng)論 1 44
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,626評(píng)論 18 399
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,886評(píng)論 0 11
  • 常去的一家小店,店內(nèi)的餐食算不上美味,但也勉強(qiáng)果腹。裝潢是清新文藝的風(fēng)格,原木桌椅,精心挑選的裝飾小物,正...
    梅子Sophy閱讀 180評(píng)論 0 0
  • 南紅珠串是南紅收藏成品中最為普遍和超值的一種,市場(chǎng)對(duì)于優(yōu)質(zhì)南紅珠串的需求也是比較大的。大家在鑒別南紅珠串優(yōu)劣時(shí),會(huì)...
    五柳文玩華掌柜閱讀 1,262評(píng)論 0 0

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