你真的瞭解String嗎?(修正版)

修正前:new出來的對象,會在堆中存放真正的值; 大錯特錯!!!!java

修正後:new出來的對象,堆存放的並非真正的值,而是常量池中字符串常量的地址。面試

1、拋磚引玉

​ 不知道你們在作面試題時是否會遇到關於String的題,記得校招時,樓主常常遇到String的題,有時候會很懵逼。先來看一個例子:sql

public class StringTest {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = "he" + "ll" + "o";
        System.out.println(str1 == str2);
        System.out.println(str1 == str3);

        String str = "o";
        String str4 = "hell" + str;
        System.out.println(str1 == str4);

        String str5 = new String("hello");
        System.out.println(str1 == str5);
        System.out.println(str4 == str5);
        System.out.println(str1.equals(str5));
        //intern()函數做用:直接獲取常量池中字符串常量的地址並返回。
        System.out.println(str1==str5.intern());

        String str6 = new String("beyond");

    }
}

//輸出的結果
//true
//true
//false
//false
//false
//true
//true
複製代碼

你答對了嗎?app

2、細說原理

1:簡單說明==與equals()區別:

  • ==:比較的是內存地址是否相同,即引用是否指向了內存中的同一個對象。指向同一個內存地址則爲true;不然,爲false。
  • equals():比較的是兩個引用指向的值是否相同。若是指向的值是相同的,則返回true,不然,返回false。

2:從內存角度詳談String

首先咱們要知道,String是Java的一個類,有兩種建立對象的方式【不考慮反射、clone、反序列化】。函數

  • 方式一:String str1 = "hello"; 將對象放到了常量池中
  • 方式二:String str2 = new String("hello"); 在堆中新建了一個對象
1554981844054
1554981844054

​ (我這個圖畫得太醜了。。。。。。[攤手])工具

​ 經過方式一建立String對象,會先查看常量池中是否有這個字符串常量,若是存在,則直接將引用指向這個常量(即例子中:str1==str2爲true)。比較疑惑的會是str3,str4。由於str3由字符串常量「he」、"ll"、「o」組成,編譯器發現這三個常量組成的字符串常量已經存在了常量池中,即編譯期間就已經肯定了最終值,則也會將引用指向「hello」字符串常量。對於str4,由於編譯器不能在編譯時就肯定字符串最終的值,因此會將字符串常量「hell」存放在常量池,再在堆中生成最終的對象【引伸一下:經過「+」鏈接字符串,底層是經過new StringBuilder()的append()方法進行拼接,因此應避免在循環中使用「+」來拼接字符串,以避免建立大量垃圾對象】。ui

​ 經過方式二而建立String對象,首先會先查看常量池中是否存在這個字符串常量,若是存在,則直接在對內存中new出新對象;不然,會先在常量池生成這個字符串常量,再在堆中生成新對象(如圖中str6)。特別注意:(new出對象的值爲常量池中這個字符串常量的地址,也就是堆中存放的都是字符串常量中的地址)spa

3:使用javap命令從編譯結果驗證

​ javap是jdk自帶的反解析工具。它的做用就是根據class字節碼文件,反解析出當前類對應的code區(彙編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。命令行

javap的用法格式:
javap <options> <classes>
其中classes就是你要反編譯的class文件。
在命令行中直接輸入javap或javap -help能夠看到javap的options有以下選項:code

-help  --help  -?        輸出此用法消息
 -version                 版本信息,實際上是當前javap所在jdk的版本信息,不是class在哪一個jdk下生成的。
 -v  -verbose             輸出附加信息(包括行號、本地變量表,反彙編等詳細信息)
 -l                         輸出行號和本地變量表
 -public                    僅顯示公共類和成員
 -protected               顯示受保護的/公共類和成員
 -package                 顯示程序包/受保護的/公共類 和成員 (默認)
 -p  -private             顯示全部類和成員
 -c                       對代碼進行反彙編
 -s                       輸出內部類型簽名
 -sysinfo                 顯示正在處理的類的系統信息 (路徑, 大小, 日期, MD5 散列)
 -constants               顯示靜態最終常量
 -classpath <path>        指定查找用戶類文件的位置
 -bootclasspath <path>    覆蓋引導類文件的位置
複製代碼

好比本例子:javap -verbose StringTest

1554891308064
1554891308064

3、總結

​ String在內存的存在形式有兩種,若是經過直接賦值的形式(方式一)會將對象直接放到常量池中【若是由幾個字符串常量拼接而成,而且在編譯時就肯定了最終的結果(即常量池中存在最終拼接而成的字符串),則直接將引用指向這個字符串常量;若是在編譯時不能肯定最終的結果,則會將最終結果在堆中生成,並其中的字符串常量會在常量池中存在】;若是經過new的方式(方式二),先在常量池生成字符串常量,再在堆中生成字符串對象,對象的值爲這個字符串常量在常量池中的地址。

小弟才疏學淺,若有錯誤,敬請指正,十分感謝。

相關文章
相關標籤/搜索