在逛 Stack Overflow 的時候,發現了一些訪問量像喜馬拉雅山一樣高的問題,比如說這個:如何比較 Java 的字符串?訪問量足足有 370萬+,這不得了啊!說明有很多很多的程序員被這個問題困擾過。
PS:系列文章回顧:《Stack Overflow 上250萬瀏覽量的一個問題:你對象丟了》
我們來回顧一下提問者的問題:
截止到目前為止,我一直使用“”操作符來比較字符串,直到程序出現了一個 bug,需要使用
.equals()
方法來解決。這是為什么呢?“”操作符和.equals()
方法之間有什么區別呢?
和提問者相反,在我剛開始學習 Java 的時候,比較字符串一直使用的是 .equals()
方法,因為不管是書本還是老師,都告誡我不要直接使用“==”操作符來比較,會出 bug。至於為什么,書本和老師都沒有幫我搞清楚。
那借此機會,我就來梳理一下 Stack Overflow 上的高贊答案,我們來一起學習進步,打怪升級。
-
“==”操作符用於比較兩個引用(內存中的存放地址)是否相等,它們是否是同一個對象。
-
.equals()
用於比較兩個對象的內容是否相等。
怎么理解這兩句話呢?我來舉個不恰當又很恰當的例子。
有一對雙胞胎,姐姐叫阿麗塔,妹妹叫洛麗塔。我們普通人的眼睛完全無法分辨誰是姐姐誰是妹妹,可她們的媽媽卻可以輕而易舉地辨認出。
.equals()
就好像我們普通人,看見阿麗塔以為是洛麗塔,看見洛麗塔以為是阿麗塔,看起來一樣就覺得她們是同一個人;“==”操作符就好像她們的媽媽,要求更嚴格,觀察更細致,一眼就能分辨出誰是姐姐誰是妹妹。
String alita = new String("小蘿莉");
String luolita = new String("小蘿莉");
System.out.println(alita.equals(luolita)); // true
System.out.println(alita == luolita); // false
就上面這段代碼來說,.equals()
輸出的結果為 true,而“==”操作符輸出的結果為 false——前者沒后者要求那么嚴格。
大家都知道,Java 的所有類都默認地繼承着 Object 這個超類,該類有一個名為 .equals()
的方法,源碼如下所示。
public boolean equals(Object obj) {
return (this == obj);
}
可以看得出,Object 類的 .equals()
方法默認采用的是“”操作符進行比較。假如子類沒有重寫該方法的話,那么“”操作符和 .equals()
方法的功效就完全一樣——比較兩個對象的內存地址或者對象的引用是否相等。
但實際情況中,有不少類重寫了 .equals()
方法,因為比較內存地址太重了,不太符合現實的場景需求。String 類就重寫了 .equals()
方法,源碼如下所示。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
可以看得出,如果兩個字符串對象“==”,那么 .equals()
的結果就為 true;否則的話,就比較兩個字符串的內容是否相等。
大家應該都知道了,創建字符串對象有兩種寫法,如下所示。
String luolita = "小蘿莉";
String alita = new String("小蘿莉");
第一種是在字符串常量池(存儲在方法區)中創建對象,並將對象的引用賦值給 luolita。第二種是通過 new 關鍵字在堆中創建對象,並將對象引用賦值給 alita。
PS:字符串作為最基礎的數據類型,使用非常頻繁,如果每次都通過 new 關鍵字進行創建,會耗費高昂的時間和空間代價。Java 虛擬機為了提高性能和減少內存開銷,就設計了字符串常量池:相同字面量的對象只有一個。
PPS:Java 虛擬機在執行程序的過程中會把內存區域划分為若干個不同的數據區域,如下圖所示。
下面我們通過實際代碼來看看字符串的比較。
第一種:
new String("小蘿莉").equals("小蘿莉") // --> true
.equals()
比較的是兩個字符串對象的內容是否相等,所以結果為 true。
第二種:
new String("小蘿莉") == "小蘿莉" // --> false
“==”操作符左側的對象存儲在堆中,右側的對象存儲在方法區,所以返回 false。
第三種:
new String("小蘿莉") == new String("小蘿莉") // --> false
new 出來的兩個對象肯定是不相等的,所以返回 false。
第四種:
"小蘿莉" == "小蘿莉" // --> true
字符串常量池中只會有一個對象,所以返回 true。
"小蘿莉" == "小" + "蘿莉" // --> true
由於“小”和“蘿莉”都在字符創常量池,所以編譯器會將其自動優化為“小蘿莉”,所以返回 true。
經過大量實例的分析,我們可以得出如下結論(也是對提問者的回答):
-
當比較兩個字符串對象的內容是否相等時,請使用
.equals()
方法。 -
當比較兩個字符串對象是否相等時,請使用“==”操作符。
當然了,如果要進行兩個字符串對象的內容比較,除了 .equals()
方法,還有其他可選的方法。
1)Objects.equals()
Objects.equals()
這個靜態方法的優勢在於不需要在調用之前判空。
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
如果直接使用 a.equals(b)
,則需要在調用之前對 a 進行判空,否則可能會拋出空指針 java.lang.NullPointerException
。
Objects.equals("小蘿莉", new String("小" + "蘿莉")) // --> true
Objects.equals(null, new String("小" + "蘿莉")); // --> false
Objects.equals(null, null) // --> true
String a = null;
a.equals(new String("小" + "蘿莉")); // throw exception
2)String 類的 .contentEquals()
.contentEquals()
的優勢在於可以將字符串與任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)進行比較。
public boolean contentEquals(CharSequence cs) {
// Argument is a StringBuffer, StringBuilder
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// Argument is a String
if (cs instanceof String) {
return equals(cs);
}
// Argument is a generic CharSequence
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}
從源碼上可以看得出,如果 cs 是 StringBuffer,該方法還會進行同步,非常的智能化。不過需要注意的是,使用該方法之前,需要確保比較的兩個字符串都不為 null,否則將會拋出空指針。
再強調一點,.equals()
方法在比較的時候需要判 null,而“==”操作符則不需要。
System.out.println( null == null); // --> true
好了各位讀者朋友們,以上就是本文的全部內容了。能看到這里的都是人才,二哥必須要為你點個贊👍。如果覺得不過癮,還想看到更多,可以查看我的個人博客。另外呢,給大家一個承諾,我每周都會更新一篇《程序人生》和一篇 Java 技術棧相關的文章,敬請期待。如果你有什么問題需要我的幫助,或者想噴我了,歡迎留言喲。
養成好習慣!如果覺得這篇文章有點用的話,求點贊、求關注、求分享、求留言,這將是我寫下去的最強動力!如果大家想要第一時間看到二哥更新的文章,可以掃描下方的二維碼,關注我的公眾號。我們下篇文章見!如果大家想要第一時間看到二哥更新的文章,可以掃描下方的二維碼,關注我的公眾號。我們下篇文章見!