最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文系統(tǒng)梳理Java中字符串比較的高頻誤區(qū)

 更新時間:2026年05月25日 08:46:23   作者:超級苦力怕  
本文圍繞?Java?字符串比較中的高頻誤區(qū)展開,系統(tǒng)梳理?==、equals()、字符串常量池、new?String()、編譯期與運行期拼接、final?常量折疊以及?intern()?的核心規(guī)則,希望幫助?Java?初學(xué)者真正理解?String?比較背后的引用關(guān)系與常見踩坑點

前言

很多 Java 初學(xué)者第一次寫字符串比較,都會下意識使用 ==。更麻煩的是,有些代碼用 == 居然也能得到 true,于是問題變得更迷惑:字符串到底能不能用 == 比?equals() 一定是比較內(nèi)容嗎?new String("hello")"hello" 到底差在哪?本文會從 ==、equals()、字符串常量池、字符串拼接和 intern() 五個角度,把這些高頻坑一次理順。

一、先給結(jié)論:字符串比較別靠感覺

先把最關(guān)鍵的結(jié)論放在前面:

  • 基本類型:== 比較值
  • 引用類型:== 比較是否同一個對象
  • equals:普通方法,默認也比較引用
  • String.equals:String 重寫后的方法,比較字符串內(nèi)容
  • 字符串字面量:會和字符串常量池有關(guān)
  • new String(...):會創(chuàng)建新的 String 對象

所以,看到下面這兩行代碼時,不能只盯著內(nèi)容是不是一樣:

String a = "hello";
String b = new String("hello");

它們的內(nèi)容一樣,但不代表它們一定是同一個對象。

寫法比較內(nèi)容比較對象是否相同是否推薦用于字符串內(nèi)容比較
a == b不推薦
a.equals(b)是,前提是 a 不為 null推薦
"hello".equals(a)推薦,可避免空指針

核心結(jié)論: 字符串內(nèi)容比較使用 equals(),不要因為某些字面量場景下 == 返回 true,就誤以為 == 在比較字符串內(nèi)容。

二、==到底比較什么

2.1 基本類型:比較值本身

如果兩邊都是基本類型,== 比較的是值。

基本類型使用 == 比較示例

int a = 10;
int b = 10;

System.out.println(a == b); // true

這里沒有對象,也沒有引用,變量里保存的就是具體數(shù)值。

2.2 引用類型:比較是否指向同一個對象

如果兩邊是引用類型,== 比較的是兩個引用是否指向同一個對象。

引用類型使用 == 比較示例

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2); // false

s1s2 的內(nèi)容都是 "hello",但它們是兩個不同的 String 對象,所以 == 返回 false

可以把引用先粗略理解成對象的“地址牌”:

s1 ---> String 對象 A,內(nèi)容是 hello
s2 ---> String 對象 B,內(nèi)容是 hello

內(nèi)容一樣,不代表地址牌一樣。

2.3 工程結(jié)論:字符串內(nèi)容用equals()

對引用類型來說,== 只回答一個問題:這兩個引用是不是指向同一個對象?

它不會逐字符比較字符串內(nèi)容。

所以業(yè)務(wù)代碼里判斷輸入、命令、狀態(tài)碼、配置值這類文本內(nèi)容時,直接用 equals(),不要用 == 賭常量池。

誤區(qū):== 不能比較字符串

正確理解: == 可以比較字符串引用,但它比較的是是否同一個對象。某些字面量場景下 == 返回 true,只是因為兩個引用剛好指向同一個池中對象,不代表 == 是字符串內(nèi)容比較工具。

三、equals()不是天生比較內(nèi)容

3.1equals()是Object里的方法

Java 中所有類最終都繼承自 Object

Object 中有一個 equals(Object obj) 方法。默認情況下,它的行為和 == 很像,也是判斷兩個引用是否指向同一個對象。

普通類未重寫 equals() 示例

class Student {
    private String name;

    public Student(String name) {
        this.name = name;
    }
}

Student s1 = new Student("Tom");
Student s2 = new Student("Tom");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // false

兩個學(xué)生對象的 name 都是 "Tom",但 Student 沒有重寫 equals(),所以這里仍然按對象引用來比較。

核心結(jié)論: equals() 不是語法規(guī)則,它只是一個方法。這個方法到底怎么比較,取決于類本身有沒有重寫它。

3.2String重寫了equals()

String 的特殊之處在于:它重寫了 equals()。

所以:

String 使用 equals() 比較內(nèi)容示例

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true

s1 == s2false,因為它們不是同一個對象。

s1.equals(s2)true,因為 String.equals() 比較的是字符串內(nèi)容。

入門階段不需要背源碼,只要知道它的大致邏輯:

  • 如果兩個引用本來就是同一個對象,直接返回 true。
  • 如果對方不是 String,返回 false。
  • 如果對方也是 String,再比較字符串內(nèi)容是否一致。

在不同 JDK 中,String 內(nèi)部存儲細節(jié)可能不同,比如早期常見 char[],后續(xù)版本可能使用更緊湊的 byte[]。但對我們使用者來說,結(jié)論不變:String.equals() 比較字符串內(nèi)容。

3.3StringBuilder的equals()是一個反例

不要把 String.equals() 的規(guī)則套到所有字符串相關(guān)類上。

StringBuilderStringBuffer 沒有像 String 那樣重寫 equals(),所以它們的 equals() 仍然是引用比較。

StringBuilder 比較示例

StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abc");

System.out.println(sb1 == sb2);              // false
System.out.println(sb1.equals(sb2));         // false
System.out.println(sb1.toString().equals(sb2.toString())); // true

如果想比較兩個 StringBuilder 里的文本內(nèi)容,可以先轉(zhuǎn)成 String,再調(diào)用 String.equals()。

四、字符串常量池:為什么有時==也會返回 true

4.1 字符串字面量會被復(fù)用

看這段代碼:

字符串字面量比較示例

String a = "hello";
String b = "hello";

System.out.println(a == b); // true

這段代碼里,a == b 返回 true

但注意,它不是因為 == 突然開始比較內(nèi)容了,而是因為兩個引用指向了同一個字符串對象。

字符串字面量 "hello" 會和字符串常量池有關(guān)??梢韵劝炎址A砍乩斫獬?JVM 管理的一塊字符串緩存:

  • 字符串字面量和編譯期常量表達式的結(jié)果會被 intern。
  • 相同內(nèi)容的字符串字面量會引用同一個 String 對象。

這里不是“盡量復(fù)用”,而是 Java 語言規(guī)范對字符串字面量共享語義的保證。具體什么時候解析、怎么放入池中,可以交給 JVM 實現(xiàn)處理;對 Java 代碼來說,相同內(nèi)容的字符串字面量會指向同一個池中對象。

所以:

a ---> 常量池中的 "hello"
b ---> 常量池中的 "hello"

兩個引用指向同一個對象,a == b 才返回 true

4.2new String("hello")會創(chuàng)建新對象

再看這段:

字面量與 new String 對比示例

String a = "hello";
String b = new String("hello");

System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

a 指向的是常量池相關(guān)的 "hello" 對象。

b 指向的是 new 出來的新 String 對象。

它們內(nèi)容一樣,但不是同一個對象。

這里還有一個經(jīng)常被說錯的點:

new String("hello") 至少會創(chuàng)建一個新的 String 對象。至于常量池中的 "hello" 是否在這一行新建,取決于池中之前是否已經(jīng)有它。

所以,不要死記成“new String("hello") 一定創(chuàng)建兩個對象”。更嚴謹?shù)恼f法是:new String("hello") 一定會創(chuàng)建一個新的堆中 String 對象;字符串字面量 "hello" 會關(guān)聯(lián)字符串常量池。

4.3 字符串常量池不等于元空間

這里順手把一個 JVM 概念坑捋清楚。

很多資料會同時提到:

  • Class 文件常量池
  • 運行時常量池
  • 字符串常量池
  • 方法區(qū)
  • 永久代
  • 元空間

這些詞很容易混在一起。

入門階段先記住下面這張表:

名稱入門理解本文是否重點
Class 文件常量池.class 文件中的常量表不是
運行時常量池類加載后,常量池在運行時的形態(tài)不是
字符串常量池JVM 用來復(fù)用字符串對象引用的結(jié)構(gòu)

在 HotSpot JVM 中:

  • JDK 6:字符串常量池主要在永久代中。
  • JDK 7+:字符串常量池移動到了堆中。
  • JDK 8+:永久代被移除,類元數(shù)據(jù)主要放到元空間中;但字符串常量池不是“搬到元空間”。

核心結(jié)論: 本文討論的是字符串常量池,也就是字符串字面量和 intern() 相關(guān)的那部分,不要把它和方法區(qū)、元空間直接畫等號。

五、字符串拼接:編譯期和運行期不是一回事

5.1 字面量拼接會被編譯器折疊

下面這段代碼很容易讓人誤判:

字符串字面量拼接示例

String a = "ab";
String b = "a" + "b";

System.out.println(a == b); // true

為什么是 true?

根據(jù) Java 語言規(guī)范,"a" + "b" 是編譯期常量表達式,編譯器必須在編譯時計算出 "ab"。

也就是說,源碼看起來像拼接,但 b 在字節(jié)碼里直接對應(yīng)的就是 "ab"

String b = "ab";

所以 ab 最終都指向常量池里的同一個 "ab"

5.2 變量參與拼接通常是運行期結(jié)果

再看這段:

變量參與字符串拼接示例

String x = "a";
String y = "b";
String z = x + y;

System.out.println("ab" == z);      // false
System.out.println("ab".equals(z)); // true

x + y 需要在程序運行時計算,結(jié)果通常是一個新的字符串對象,不會自動等同于常量池中的 "ab" 引用。

老版本字節(jié)碼中,運行期字符串拼接常表現(xiàn)為 StringBuilder;JDK 9 以后,底層可能使用 invokedynamicStringConcatFactory。這些實現(xiàn)細節(jié)入門階段不用展開。

對工程判斷來說,關(guān)鍵推論不變:無論底層是 StringBuilder 還是 invokedynamic,運行期拼接產(chǎn)生的結(jié)果都不能依賴 == 與常量池對象相等。

只要抓住一句話:

  • 字面量拼接屬于編譯期常量表達式,會在編譯期計算。
  • 變量參與拼接通常是運行期結(jié)果。
  • 判斷內(nèi)容是否相同,仍然用 equals()。

5.3final常量的特殊情況

如果變量是編譯期常量,也可能被編譯器提前折疊。

final 編譯期常量拼接示例

final String x = "a";
final String y = "b";
String z = x + y;

System.out.println("ab" == z); // true

這里 xy 都是編譯期可以確定的常量,所以 x + y 可以被優(yōu)化成 "ab"。

更具體一點,能被編譯器折疊的 String 變量通常要滿足這些條件:

  • 使用 final 修飾。
  • 聲明時直接用字符串字面量或其他編譯期常量表達式初始化。
  • 編譯器在編譯階段就能確定它的值。

反過來,如果 final 變量的值來自運行期對象創(chuàng)建,就不能按編譯期常量處理。

final 但不能編譯期折疊的示例

final String x = new String("a");
final String y = "b";
String z = x + y;

System.out.println("ab" == z); // false

這里 x 雖然是 final,但 new String("a") 是運行期創(chuàng)建對象,不是編譯期常量表達式。

六、intern():手動拿到池中的引用

6.1intern()的基本作用

intern() 可以理解成:返回字符串常量池中與當(dāng)前字符串內(nèi)容相同的那個規(guī)范引用。

intern 基本示例

String a = new String("hello");
String b = a.intern();
String c = "hello";

System.out.println(a == b); // false
System.out.println(b == c); // true

這里可以這樣理解:

  • a 指向 new 出來的 String 對象。
  • b 是 intern() 返回的池中引用。
  • c 是字面量引用,也指向池中的 "hello"。

所以 b == ctrue。

6.2intern()不適合死背特殊輸出

網(wǎng)上經(jīng)常會看到這種題:

intern 進階案例

String s1 = new StringBuilder().append("think").append("123").toString();
System.out.println(s1.intern() == s1);

String s2 = new StringBuilder().append("ja").append("va").toString();
System.out.println(s2.intern() == s2);

以常見 HotSpot JDK 8 環(huán)境為例,很多時候會看到這樣的結(jié)果:

true
false

為什么第一個可能是 true?

new StringBuilder().append("think").append("123").toString() 會在運行期生成一個內(nèi)容為 "think123" 的新 String 對象。通常情況下,字符串常量池里原本沒有 "think123"。在 JDK 7+ 的 HotSpot 中,字符串常量池已經(jīng)在堆中,調(diào)用 s1.intern() 時,池里如果沒有這個內(nèi)容,就可能把當(dāng)前這個堆中字符串對象的引用放進去。因此:

  • s1.intern() 返回的就是 s1 指向的對象
  • 所以 s1.intern() == s1 為 true

為什么第二個可能是 false?

"java" 這個字符串比較特殊。在 JDK 8 等環(huán)境中,它很可能在當(dāng)前代碼執(zhí)行前,已經(jīng)被 JVM 或類庫內(nèi)部提前放入字符串常量池。此時 s2 是運行期新創(chuàng)建的對象,而 s2.intern() 返回的是池里早就存在的那個 "java" 引用。因此:

  • s2 指向新對象
  • s2.intern() 返回舊的池中對象
  • 所以 s2.intern() == s2 為 false

這段例子真正想說明的不是“永遠背 true、false”,而是下面這個判斷步驟:

更適合記住的是規(guī)則:

  • intern() 返回池中內(nèi)容相同字符串的引用。
  • 如果池中已經(jīng)有,返回已有引用。
  • 如果池中沒有,會嘗試把當(dāng)前字符串對應(yīng)內(nèi)容放入池中,再返回池中的引用。

JDK 6、JDK 7+、JDK 11/12 以后,以及不同啟動流程下,某些字符串是否提前進入池中可能不同。所以這類題要按規(guī)則分析,不要只背輸出。

6.3 實戰(zhàn)中不要亂用intern()

intern() 不是性能優(yōu)化萬能藥。

字符串常量池本質(zhì)上也需要管理和查找。如果盲目把大量動態(tài)字符串都 intern(),可能帶來額外內(nèi)存占用和查找成本。

普通業(yè)務(wù)代碼里,優(yōu)先做到:

  • 字符串內(nèi)容比較用 equals()。
  • 不要用 == 判斷字符串內(nèi)容。
  • 真正需要大量重復(fù)字符串去重時,再評估是否使用 intern() 或其他更合適的緩存方案。

七、為什么String要設(shè)計成不可變

字符串常量池能安全復(fù)用,有一個重要前提:String 是不可變的。

假設(shè) String 可以被隨便修改,會發(fā)生什么?

String a = "hello";
String b = "hello";

如果 ab 指向同一個常量池對象,而 a 可以把內(nèi)容改成 "java",那 b 看到的內(nèi)容也會被影響。這顯然很危險。

所以,String 不可變帶來幾個好處:

好處說明
支持常量池復(fù)用多個引用可以安全共享同一個字符串對象
線程更安全不可變對象天然更適合多線程共享
適合作為 Map 的 key內(nèi)容不變,哈希值穩(wěn)定
便于緩存 hashCode字符串內(nèi)容不變,計算過的哈希值可以復(fù)用

這也是為什么后面學(xué)習(xí) HashMap、HashSet 時,經(jīng)常會看到 String 被當(dāng)作 key 或去重元素。

核心結(jié)論: String 的不可變性不是擺設(shè),它是字符串常量池、線程安全和哈希結(jié)構(gòu)穩(wěn)定性的基礎(chǔ)。

八、幾個真實容易踩的坑

8.1 用==判斷字符串內(nèi)容

錯誤示例

String input = new String("yes");

if (input == "yes") {
    System.out.println("通過");
}

這段代碼不可靠,因為 input"yes" 不一定指向同一個對象。

正確寫法

String input = new String("yes");

if ("yes".equals(input)) {
    System.out.println("通過");
}

把常量字符串寫在前面,可以避免 inputnull 時調(diào)用 input.equals(...) 拋出 NullPointerException

8.2 忽略equals()區(qū)分大小寫

equals() 是嚴格內(nèi)容比較,大小寫不同就不相等。

大小寫敏感比較示例

String input = "YES";

System.out.println("yes".equals(input));           // false
System.out.println("yes".equalsIgnoreCase(input)); // true

如果業(yè)務(wù)規(guī)則允許大小寫不敏感,例如用戶輸入 YESyes、Yes 都算確認,可以使用 equalsIgnoreCase()。

但它只忽略大小寫,不會自動忽略空格:

System.out.println("yes".equalsIgnoreCase(" yes ")); // false

如果輸入來自用戶,通常還要先考慮是否需要 trim() 去掉首尾空白。

8.3 以為equals()永遠比較內(nèi)容

易錯示例

StringBuilder a = new StringBuilder("abc");
StringBuilder b = new StringBuilder("abc");

System.out.println(a.equals(b)); // false

不是所有類都按內(nèi)容重寫了 equals()

如果是自己寫的類,要根據(jù)業(yè)務(wù)語義決定是否重寫 equals()hashCode()

8.4 以為new String("abc")和"abc"一樣

易錯示例

String a = "abc";
String b = new String("abc");

System.out.println(a == b); // false

內(nèi)容一樣,只說明 a.equals(b)true

是否同一個對象,是另一回事。

8.5 把常量池位置說錯

容易說錯的表達:JDK 8 以后字符串常量池在元空間。

更準確的表達:

  • HotSpot JDK 7+ 字符串常量池在堆中。
  • JDK 8 移除永久代,使用元空間存放類元數(shù)據(jù)。

這兩個變化有關(guān)聯(lián),但不是同一件事。

總結(jié)

先用一張速查表收束:

問題結(jié)論
基本類型能不能用 ==能,比較值
字符串內(nèi)容能不能用 ==不要用,== 比較引用
equals() 是否一定比較內(nèi)容不一定,取決于類有沒有重寫
String.equals() 比較什么比較字符串內(nèi)容
equals() 是否忽略大小寫不忽略,大小寫不敏感時用 equalsIgnoreCase()
字符串字面量為什么 == 可能為 true因為常量池復(fù)用了同一個對象
new String("abc") 要注意什么會創(chuàng)建新的 String 對象,不要和字面量引用混為一談
intern() 做什么返回字符串常量池中的規(guī)范引用

再把本文壓縮成三句話:

  • == 對引用類型比較的是“是否同一個對象”。
  • String.equals() 比較的是字符串內(nèi)容。
  • 字符串常量池會讓某些 == 看起來“剛好正確”,但這不是判斷內(nèi)容的可靠方式。

核心結(jié)論: 寫業(yè)務(wù)代碼時,字符串內(nèi)容比較優(yōu)先使用 equals();只有你明確要判斷兩個引用是否指向同一個對象時,才考慮使用 ==

排查清單

如果字符串比較結(jié)果和你預(yù)期不一致,可以按這 5 個問題快速自查:

自查問題處理方式
是不是用 == 比較字符串內(nèi)容了?改成 equals()
是不是和字面量用 == 比較,剛好被常量池“騙”了?不依賴 ==,統(tǒng)一按內(nèi)容比較
有沒有大小寫問題?需要忽略大小寫時用 equalsIgnoreCase()
是不是在比較 StringBuilder / StringBuffer 的內(nèi)容?toString(),再比較字符串內(nèi)容
是不是把 new String(...) 和字面量混用了?記住內(nèi)容相同不代表對象相同

以上就是一文系統(tǒng)梳理Java中字符串比較的高頻誤區(qū)的詳細內(nèi)容,更多關(guān)于Java字符串比較的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • nacos配置讀取實現(xiàn)過程

    nacos配置讀取實現(xiàn)過程

    本文介紹了在Spring Boot應(yīng)用中如何指定和使用配置文件,創(chuàng)建配置類讀取Nacos配置,并通過接口返回給前端,同時使用@RefreshScope實現(xiàn)配置實時刷新
    2026-01-01
  • Java實現(xiàn)線程通信的案例講解

    Java實現(xiàn)線程通信的案例講解

    所謂線程通信就是線程間相互發(fā)送數(shù)據(jù),線程通信通常通過共享一個數(shù)據(jù)的方式實現(xiàn)。本文將通過案例詳解Java中線程通信的實現(xiàn),感興趣的可以了解一下
    2022-05-05
  • Java中使用byte[]獲取16位字符串的技巧分享

    Java中使用byte[]獲取16位字符串的技巧分享

    在Java編程中,處理字符編碼和字節(jié)數(shù)組時,尤其是從??byte[]???數(shù)組中獲取字符串,可能會遇到字符集的復(fù)雜性問題,本文將介紹如何從一個??byte[]??數(shù)組中正確獲取16位字符串,需要的朋友可以參考下
    2024-08-08
  • Spring Boot集成Quartz注入Spring管理的類的方法

    Spring Boot集成Quartz注入Spring管理的類的方法

    本篇文章主要介紹了Spring Boot集成Quartz注入Spring管理的類的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • 淺談Java內(nèi)部類——靜態(tài)內(nèi)部類

    淺談Java內(nèi)部類——靜態(tài)內(nèi)部類

    這篇文章主要介紹了Java靜態(tài)內(nèi)部類的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java內(nèi)部類的相關(guān)知識,感興趣的朋友可以了解下
    2020-08-08
  • Dubbo本地調(diào)試的幾種方式總結(jié)

    Dubbo本地調(diào)試的幾種方式總結(jié)

    dubbo服務(wù)方啟動時需要加載的東西太多,如果跑單元測試把服務(wù)開啟會浪費不少時間,而且單元測試沒法保持服務(wù)一直開啟的狀態(tài),這篇文章主要給大家介紹了關(guān)于Dubbo本地調(diào)試的幾種方式,需要的朋友可以參考下
    2022-11-11
  • Spring MVC文件上傳大小和類型限制以及超大文件上傳bug問題

    Spring MVC文件上傳大小和類型限制以及超大文件上傳bug問題

    這篇文章主要介紹了Spring MVC文件上傳大小和類型限制以及超大文件上傳bug問題,非常具有實用價值,需要的朋友可以參考下
    2017-10-10
  • SpringBoot使用POI進行Excel下載

    SpringBoot使用POI進行Excel下載

    這篇文章主要為大家詳細介紹了SpringBoot使用POI進行Excel下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • mybatis 插件: 打印 sql 及其執(zhí)行時間實現(xiàn)方法

    mybatis 插件: 打印 sql 及其執(zhí)行時間實現(xiàn)方法

    下面小編就為大家?guī)硪黄猰ybatis 插件: 打印 sql 及其執(zhí)行時間實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • Java web velocity分頁宏示例

    Java web velocity分頁宏示例

    這篇文章主要介紹了Java web velocity分頁宏示例,需要的朋友可以參考下
    2014-03-03

最新評論

靖远县| 邹城市| 东源县| 信丰县| 景德镇市| 延长县| 揭东县| 丰镇市| 花莲县| 湖北省| 嫩江县| 元江| 岚皋县| 二连浩特市| 湖南省| 前郭尔| 南靖县| 华阴市| 敦煌市| 邢台市| 正阳县| 綦江县| 宜兰市| 哈密市| 搜索| 收藏| 万全县| 白沙| 张掖市| 正定县| 时尚| 浪卡子县| 门源| 保定市| 安塞县| 白银市| 泸溪县| 鹤庆县| 当阳市| 金溪县| 枞阳县|