【十八掌●基本功篇】第一掌:Java之String的equals方法

這一篇博文是【大數據技術●降龍十八掌】系列文章的其中一篇,點擊查看目錄:這裏寫圖片描述大數據技術●降龍十八掌


1、Java的一個例子
public static void main(String[] arge) {

        //1
        String str1 = new String("1234");
        String str2 = new String("1234");
        System.out.println("new String()==:" + (str1 == str2));
        //String爲引用類型,str1和str2爲新實例化出來的對象,分別指向不同的內存地址。而==對於引用類型判斷,是判斷的是引用地址,所以例子1結果爲false。

        //2
        String str3 = "1234";
        String str4 = "1234";
        System.out.println("常量字符串==:" + (str3 == str4));
        //對於第二個例子,編譯器編譯代碼時,會將"1234"當做一個常量,並保存在JVM的常量池中,然後編譯String str3="1234";時,將常量的指針賦值給str3,在編譯String str4="1234";時,編譯器查找常量池裏有相同的常量,就將存在的常量指針賦給str4,這樣結果就是str3和str4都指向了常量池中的常量的地址,所以==比較結果爲true;

        //3
        String str5 = "1234";
        String str6 = "12" + "34";
        System.out.println("常量表達式==:" + (str5 == str6));
        //第三個例子,編譯時編譯器發現能夠計算出"12"+"34"的值,它是個常量表達式,就按照第二個例子一樣處理,最終str5和str6都指向了同一個內存地址。所以==比較結果爲true;

        //4
        String str7 = "1234";
        String str8 = "12" + 34;
        System.out.println("字符串和數字相加的表達式==:" + (str7 == str8));
        //第四個例子、第五個例子和第六個例子,類似第三個例子,編譯時編譯器發現是常量表達式,能夠計算出值,就儘量計算出來,所以==比較結果爲true;

        //5
        String str9 = "12true";
        String str10 = "12" + true;
        System.out.println("字符串和Boolen相加表達式==:" + (str9 == str10));

        //6
        final String val = "34";
        String str11 = "1234";
        String str12 = "12" + val;
        System.out.println("字符串和常量相加的表達式==:" + (str11 == str12));

        //7
        String str13 = "1234";
        String str14 = "12" + getVal();
        System.out.println("字符串和函數得來的常量相加表達式==:" + (str13 == str14));
        //第七個例子中,編譯器發現str14值是要調用函數才能計算出來的,是要在運行時才能確定結果的,所以編譯器就設置爲運行時執行到String str14="12" + getVal();時 要重新分配內存空間,導致str13和str1是指向兩個不同的內存地址,所以==比較結果爲false;
    }
    private static String getVal()
    {
        return "34";
    }

運行輸出:    

new String()==:false
常量字符串==:true
常量表達式==:true
字符串和數字相加的表達式==:true
字符串和Boolen相加表達式==:true
字符串和常量相加的表達式==:true
字符串和函數得來的常量相加表達式==:false

2、Java中的equals總結

(1) String是引用類型;==是關係運算符,==比較兩個引用類型時,判斷的依據是:雙方是否是指向了同一個內存地址。Java中的==,就是個關係運算符,始終遵守着它自己的規則,即:值類型比較久比較值,引用類型比較就比較內存地址。

(2) String類中的equals()方法是重寫了Object類的equals()方法,始終是比較兩個字符串是否一樣。

(3) 在編譯器編譯代碼時,指定的字符串是個常量表達式,String類型的是引用類型,編譯器先將常量表達式的內存地址賦值給一個常量,之後用到同樣的字符串時,首先查看當前作用域中是否存在了同值的常量,如果存在就使用這個常量,導致指向了同一個內存地址。

3、C#下的一個例子

static void Main(string[] args)
{
    //1
    String str1 = new String(new char[] { '1', '2', '3', '4', });
    String str2 = new String(new char[] { '1', '2', '3', '4', });
    Console.WriteLine("①new String()方式下==:" + (str1 == str2));            
    Console.WriteLine("②new String()方式下equals:" + str1.Equals(str2));
    //C#裏重寫了==運算符,使得==比較的不再是引用,而是值,所以==和Equals方法是完全一致的了
    //例子1中結果都是True

    //2
    String str3 = "1234";
    String str4 = "1234";
    Console.WriteLine("③賦值常量方式下==:" + (str3 == str4));
    Console.WriteLine("④賦值常量方式下equals:" + str3.Equals(str4));
    //結果也是True

    //3
    String val = "1234";
    String str5 = val;
    String str6 = val;
    Console.WriteLine("⑤賦值變量方式下==:" + (str5 == str6));
    Console.WriteLine("⑥賦值變量方式下equals:" + str5.Equals(str6));
    //結果也是True

    Console.ReadLine();
}

運行輸出:

①new String()方式下==:True
②new String()方式下equals:True
③賦值常量方式下==:True
④賦值常量方式下equals:True
⑤賦值變量方式下==:True
⑥賦值變量方式下equals:True

5、代碼分析

(0) String類的Equals方法的實現,是實現對字符串是否相同的比較。

public override bool Equals(object obj)
{
    if (this == null)
    {
        throw new NullReferenceException();
    }
    string strB = obj as string;
    if (strB == null)
    {
        return false;
    }
    if (this == obj)
    {
        return true;
    }
    if (this.Length != strB.Length)
    {
        return false;
    }
    return EqualsHelper(this, strB);
}

private static unsafe bool EqualsHelper(string strA, string strB)
{
    int length = strA.Length;
    fixed (char* chRef = &strA.m_firstChar)
    {
        fixed (char* chRef2 = &strB.m_firstChar)
        {
            char* chPtr = chRef;
            char* chPtr2 = chRef2;
            while (length >= 10)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 2))) != *(((int*) (chPtr2 + 2))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 4))) != *(((int*) (chPtr2 + 4))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 6))) != *(((int*) (chPtr2 + 6))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 8))) != *(((int*) (chPtr2 + 8))))
                {
                    return false;
                }
                chPtr += 10;
                chPtr2 += 10;
                length -= 10;
            }
            while (length > 0)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    break;
                }
                chPtr += 2;
                chPtr2 += 2;
                length -= 2;
            }
            return (length <= 0);
        }
    }
}

(1)String類中對==關係運算符進行了重寫,也是實現了對字符串是否相同的比較。

public static bool operator ==(string a, string b)
{
    return Equals(a, b);
}

public static bool Equals(string a, string b)
{
    if (a == b)
    {
        return true;
    }
    if ((a == null) || (b == null))
    {
        return false;
    }
    if (a.Length != b.Length)
    {
        return false;
    }
    return EqualsHelper(a, b);
}

private static unsafe bool EqualsHelper(string strA, string strB)
{
    int length = strA.Length;
    fixed (char* chRef = &strA.m_firstChar)
    {
        fixed (char* chRef2 = &strB.m_firstChar)
        {
            char* chPtr = chRef;
            char* chPtr2 = chRef2;
            while (length >= 10)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 2))) != *(((int*) (chPtr2 + 2))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 4))) != *(((int*) (chPtr2 + 4))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 6))) != *(((int*) (chPtr2 + 6))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 8))) != *(((int*) (chPtr2 + 8))))
                {
                    return false;
                }
                chPtr += 10;
                chPtr2 += 10;
                length -= 10;
            }
            while (length > 0)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    break;
                }
                chPtr += 2;
                chPtr2 += 2;
                length -= 2;
            }
            return (length <= 0);
        }
    }
}

(2) C#下String類也是引用類型,它通過重寫Equals方法和==關係運算,最終都是通過EqualsHelper方法進行對字符串的比較,所以Equals和==實質上是相同的,沒有區別。

(3) 根據以上分析,所以返回值都是True,都是根據字符串是否相同進行比較,跟對象引用沒有關係,導致結果是讓人感覺String是個值類型,其實是微軟通過對String進行改造,使之像是個值類型而已。

6、總結

(1) C#下String類中的==已經不是一個純粹的關係運算符了,它的作用是比較兩個字符串是否一樣的,同Equals方法一致。

(2) 爲什麼微軟將==改寫,而不是保持==是個關係運算符的本質呢?

MSDN上的話是:”儘管 string 是引用類型,但定義相等運算符(== 和 !=)是爲了比較 string 對象(而不是引用)的值。 這使得對字符串相等性的測試更爲直觀。”

但是我想,這個根本原因是理念不同導致的。大家都知道C#是後來借鑑Java來設計的,當初要修改Java的String設計,對==進行重寫,初衷是爲了更方便用戶的理解,所以就算稍微違反一些語言程序的統一性、完成性、獨立性等等各種性,也要修改這個設計,這就是一個微軟對於商業產品的理念:”用戶體驗”更重要。

發佈了74 篇原創文章 · 獲贊 77 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章