頭條面試官問了幾個equals的問題,我竟然沒答上來!嗚嗚嗚!

寫在前面
學習很難,克服惰性。每天學一點,不會的就少一點。
懦夫從不啓程,弱者死於路中,只剩我們前行,一步都不能停。
養成習慣很重要,先從點贊開始吧!關注[程序員之道],程序員之路不再迷茫!

擦,這兩個值明明應該是相等的啊,爲啥我用==判斷的結果時不等於,真是活見鬼了。我來debug看看。關於對象、值一些相等的判斷不知道你有沒有踩過坑,或者面試的時候有沒有被面試官坑過?我就被頭條面試官坑過。相等判斷幾連問,直接暈倒。
在這裏插入圖片描述
如果你還沒注意過這些問題,那好好看看下面的內容吧。

怎麼判斷相等

使用java編程時,經常寫一些判斷相等的代碼,應該使用==還是equals呢?

  1. 運算符==針對的是值的相等判斷,應用類型是基本數據類型,說到基本數據類型,你應該不陌生吧,java裏的基本數據類型有char、byte、short、int、long、double、float、bool,但這裏針對的基本數據類型是char、byte、short、int、long,對於double、float的等於判斷(涉及精度問題),應該是二者的差值在一個區間內才認爲是相等,bool一般只會判斷true或false,很少會使用這個判等運算符。
  2. equals針對的是引用類型,也就是實際地址裏存儲的對象內容相等,可以理解爲c語言裏指針內容判等。equals使用的場景是類的對象(包括包裝類型Integer、Short等),實際比較的是兩個地址的內容是否相等一致。

這幾個問題都能回答對嗎?

但是在實際使用的時候,因爲java jvm幫我們做了一些cache的優化,還是有一些坑點的,以下幾個問題都可以回答對嗎?
在這裏插入圖片描述

  • 使用 == 對兩個值爲 99 的直接賦值的 Integer 對象判等;
  • 使用 == 對兩個值爲 128 的直接賦值的 Integer 對象判等;
  • 使用 == 對一個值爲 99 的直接賦值的 Integer 和另一個通過 new Integer 聲明的值爲 99 的對象判等;
  • 使用 == 對一個值爲 128 的直接賦值的 Integer 和另一個通過 new Integer 聲明的值爲128 的對象判等;
  • 使用 == 對兩個通過 new Integer 聲明的值爲 99 的對象判等;
  • 使用 == 對一個值爲 128 的直接賦值的 Integer 對象和另一個值爲 128 的 int 基本類型判等。

正確結果

    public static void main(String[] args) {
        boolean result;
        Integer a = 99;
        Integer b = 99;
        result = a == b;
        System.out.println("Interger a = 99 is == Interger b = 99:" + result);

        Integer c = 128;
        Integer d = 128;
        result = c == d;
        System.out.println("Interger c = 128 is == Interger d = 128:" + result);

        Integer e = 99;
        Integer f = new Integer(99);
        result = e == f;
        System.out.println("Interger e = 99 is == Interger f = new Integer(99):" + result);

        Integer g = 128;
        Integer h = new Integer(128);
        result = g == h;
        System.out.println("Interger g = 128 is == Interger h = new Integer(128):" + result);

        Integer i = new Integer(99);
        Integer j = new Integer(99);
        result = i == j;
        System.out.println("Interger i = new Integer(99) is == j = new Integer(99):" + result);

        Integer k = 128;
        int l = 128;
        result = k == l;
        System.out.println("Integer k = 128 is == int l = 128:" + result);
    }
}

大家簡單的在腦子裏過一下程序,不知道這幾個例子你都能回答對嗎?

先來看下運行結果:
Interger a = 99 is == Interger b = 99true
Interger c = 128 is == Interger d = 128false
Interger e = 99 is == Interger f = new Integer(99)false
Interger g = 128 is == Interger h = new Integer(128)false
Interger i = new Integer(99) is == j = new Integer(99)false
Integer k = 128 is == int l = 128true

Process finished with exit code 0

IntegerCache搞的鬼

首先,對於Integer a = 99的這種直接賦值,jvm會編譯優化成Interger.valueOf(99),而對於-128~127之間的值,對於Integer類型,爲了優化內存使用,jvm對於這個範圍內的值,初始化了內存cache,也可以加快訪問速度,防止重複建立對象,可以看一下java的源碼是怎麼實現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);
    }

默認cache的區間是-128~127,也可以自定義cache的high區間。

    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() {}
    }

結果分析

知道了這些,我們來分析下剛纔的幾個例子運行結果爲什麼是那樣的?首先明確,==比較的是地址,而非地址的內容,其次有IntegerCache在搞怪。

  • 值爲99的兩個Integer對象直接賦值,編譯優化成valueOf(99),都是IntegerCache的同一個對象,所以結果爲true。
  • 兩個值爲 128 的直接賦值的 Integer 對象超出了IntegerCache的範圍,會直接new一個Integer對象,所以不等。設置 JVM 參數加上 -XX:AutoBoxCacheMax=2000,保證128在cache的範圍區間內,返回結果就會是true了。
  • 一個值爲 99 的直接賦值的 Integer 和另一個通過 new Integer 聲明的值爲 99 的對象,一個是IntegerCache裏的對象,一個是new出的Integer對象,地址肯定不同嘛,所以爲false。
  • 一個值爲 128 的直接賦值的 Integer 和另一個通過 new Integer 聲明的值爲128 的對象,這個跟例子2是一樣的,相當於都是new了兩個Integer對象,當然是不同的,返回false。
  • 對兩個通過 new Integer 聲明的值爲 99 的對象,還是一樣的嘛,都是new出的對象,比較必然不等,返回false。
  • 對一個值爲 128 的直接賦值的 Integer 對象和另一個值爲 128 的 int 基本類型,這裏要注意Integer對象和int型的值比較,java會進行自動拆箱,都退化成int型的值比較,所以最終結果肯定是相等的了。

所以對於對象相等的比較請使用equals,而非==,==是用於值比較的。
在我們的平時開發中,也要注意類中有用到Integer聲明的變量,比較相等時,一定要使用equals,如果使用==比較,可能會出現莫名其妙的錯誤(-128~127之間的比較正常,其他值的比較就不符合預期了)。

對於使用==判斷對象相等,Intellij的插件也會提示我們應該使用equals。
在這裏插入圖片描述

String對象又怎麼判斷相等呢?

String是對象,那判斷相等的方式肯定是使用equal了。但如果用==判斷會有什麼後果呢?這裏也一起看一下。

    public static void stringEqual() {
        boolean result;
        String a = "this";
        String b = "this";
        result = a == b;
        System.out.println("String a = this is == String b = this:" + result);

        String c = new String("is");
        String d = new String("is");
        result = c == d;
        System.out.println("String c = new String(is) is == String d = new String(is):" + result);

        String e = new String("a").intern();
        String f = new String("a").intern();
        result = e == f;
        System.out.println("String e = new String(a).intern() is == String f = new String(a).intern():" + result);

        String g = new String("test");
        String h = new String("test");
        result = g.equals(h);
        System.out.println("String g = new String(test) is == String h = new String(test):" + result);
    }

    public static void main(String[] args) {
        stringEqual();
    }

先看下運行結果:

String a = this is == String b = thistrue
String c = new String(is) is == String d = new String(is)false
String e = new String(a).intern() is == String f = new String(a).intern()true
String g = new String(test) is == String h = new String(test)true

Process finished with exit code 0

String類的設計,也借鑑了Integer的cache緩存,java的設計之初就是爲了節省內存的,所以String對象也是有常量池的。

解釋一下剛纔幾個例子的運行結果:

  • 通過String=“xxx"常量賦值的方式,jvm會檢查當前常量池裏有沒有這個字符"xxx”,如果沒有,會新建一個對象,放到常量池裏,如果已經存在就會返回常量池裏的對象。所以這種賦值的方式,返回的是常量池的同一個對象,所以返回的結果就是true。
  • 通過new對象的方式,是在jvm堆上重新建立一個對象,所以返回的對象地址是不同的,這也就解釋了這個例子返回的是false。
  • 通過String.intern()這種方式,是強制將對象放到字符串常量池裏,所以通過這種方式,對於同一個字符串,返回的結果一定是true。可以參見另一個鏈接:java基礎面試題-String深入理解,但這裏要注意string.intern()也不能濫用,因爲字符串常量表是用map來維護的,而且map是有固定容量的,所以對象如果太多的話,map中每個index下的值會退化成一個比較長的鏈表,查詢效率大大下降。所以使用intern的字符串對象太多的話,效率反而不高。
  • 通過equals方式的比較的是對象的值,而非兩個對象是同一個對象,是正確的對象比較方式,所以這個例子會返回true。

寫在後面
關於equals到這裏還沒有完,java是如何判斷兩個對象的equals,set是怎麼去重的?砥礪前行,永不停止,我們下篇見!
堅持和習慣是學習中的兩大絆腳石,先從養成點贊和評論開始吧,😁!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章