這一篇博文是【大數據技術●降龍十八掌】系列文章的其中一篇,點擊查看目錄:大數據技術●降龍十八掌
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設計,對==進行重寫,初衷是爲了更方便用戶的理解,所以就算稍微違反一些語言程序的統一性、完成性、獨立性等等各種性,也要修改這個設計,這就是一個微軟對於商業產品的理念:”用戶體驗”更重要。