從網(wǎng)上復(fù)制的,看別人的比較全面呐萨,自己搬過來,方便以后查找莽囤。
原鏈接:https://www.cnblogs.com/xiaoxi/p/6036701.html
一谬擦、String類
1)String類是final類,也即意味著String類不能被繼承朽缎,并且它的成員方法都默認(rèn)為final方法惨远。
2)String類其實是通過char數(shù)組來保存字符串的。
3)String對象一旦被創(chuàng)建就是固定不變的了话肖,對String對象的任何改變都不影響到原對象北秽,相關(guān)的任何change操作都會生成新的對象
二、字符串常量池
JVM為了提高性能和減少內(nèi)存的開銷最筒,在實例化字符串的時候進(jìn)行了一些優(yōu)化:使用字符串常量池贺氓。每當(dāng)我們創(chuàng)建字符串常量時,JVM會首先檢查字符串常量池床蜘,如果該字符串已經(jīng)存在常量池中辙培,那么就直接返回常量池中的實例引用。如果字符串不存在常量池中邢锯,就會實例化該字符串并且將其放到常量池中虏冻。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串。
Java中的常量池弹囚,實際上分為兩種形態(tài):靜態(tài)常量池和運行時常量池厨相。
所謂靜態(tài)常量池,即*.class文件中的常量池鸥鹉,class文件中的常量池不僅僅包含字符串(數(shù)字)字面量蛮穿,還包含類、方法的信息毁渗,占用class文件絕大部分空間践磅。
而運行時常量池,則是jvm虛擬機(jī)在完成類裝載操作后灸异,將class文件中的常量池載入到內(nèi)存中府适,并保存在方法區(qū)中羔飞,我們常說的常量池,就是指方法區(qū)中的運行時常量池檐春。
String a = "abc";//常量池中
String a = new String("abc");//保存在堆中的對象逻淌、常量池中的串
new關(guān)鍵字一定會產(chǎn)生一個對象,同時這個對象是存儲在堆中疟暖。
//采用字面值的方式賦值
String str1="aaa";
String str2="aaa";
System.out.println("===========test1============");
System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一個對象
執(zhí)行上述代碼卡儒,結(jié)果為:true。
分析:當(dāng)執(zhí)行String str1="aaa"時俐巴,JVM首先會去字符串池中查找是否存在"aaa"這個對象骨望,如果不存在,則在字符串池中創(chuàng)建"aaa"這個對象欣舵,然后將池中"aaa"這個對象的引用地址返回給字符串常量str1擎鸠,這樣str1會指向池中"aaa"這個字符串對象;如果存在缘圈,則不創(chuàng)建任何對象糠亩,直接將池中"aaa"這個對象的地址返回,賦給字符串常量准验。當(dāng)創(chuàng)建字符串對象str2時,字符串池中已經(jīng)存在"aaa"這個對象廷没,直接把對象"aaa"的引用地址返回給str2糊饱,這樣str2指向了池中"aaa"這個對象,也就是說str1和str2指向了同一個對象颠黎,因此語句System.out.println(str1 == str2)輸出:true另锋。
//采用new關(guān)鍵字新建一個字符串對象
String str3=new String("aaa");
String str4=new String("aaa");
System.out.println("===========test2============");
System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的對象
執(zhí)行上述代碼,結(jié)果為:false狭归。
分析: 采用new關(guān)鍵字新建一個字符串對象時夭坪,JVM首先在字符串池中查找有沒有"aaa"這個字符串對象,如果有过椎,則不在池中再去創(chuàng)建"aaa"這個對象了室梅,直接在堆中創(chuàng)建一個"aaa"字符串對象,然后將堆中的這個"aaa"對象的地址返回賦給引用str3疚宇,這樣亡鼠,str3就指向了堆中創(chuàng)建的這個"aaa"字符串對象;如果沒有敷待,則首先在字符串池中創(chuàng)建一個"aaa"字符串對象间涵,然后再在堆中創(chuàng)建一個"aaa"字符串對象,然后將堆中這個"aaa"字符串對象的地址返回賦給str3引用榜揖,這樣勾哩,str3指向了堆中創(chuàng)建的這個"aaa"字符串對象抗蠢。當(dāng)執(zhí)行String str4=new String("aaa")時, 因為采用new關(guān)鍵字創(chuàng)建對象時思劳,每次new出來的都是一個新的對象迅矛,也即是說引用str3和str4指向的是兩個不同的對象,因此語句System.out.println(str3 == str4)輸出:false敢艰。
編譯期確定
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println("===========test3============");
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個對象
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個對象
執(zhí)行上述代碼诬乞,結(jié)果為:true、true钠导。
分析:因為例子中的s0和s1中的"helloworld”都是字符串常量震嫉,它們在編譯期就被確定了,所以s0==s1為true牡属;而"hello”和"world”也都是字符串常量票堵,當(dāng)一個字符串由多個字符串常量連接而成時,它自己肯定也是字符串常量逮栅,所以s2也同樣在編譯期就被解析為一個字符串常量悴势,所以s2也是常量池中"helloworld”的一個引用。所以我們得出s0==s1==s2措伐。
編譯期無法確定
String s0="helloworld";
String s1=new String("helloworld");
String s2="hello" + new String("world");
System.out.println("===========test4============");
System.out.println( s0==s1 ); //false
System.out.println( s0==s2 ); //false
System.out.println( s1==s2 ); //false
執(zhí)行上述代碼特纤,結(jié)果為:false、false侥加、false捧存。
分析:用new String() 創(chuàng)建的字符串不是常量,不能在編譯期就確定担败,所以new String() 創(chuàng)建的字符串不放入常量池中昔穴,它們有自己的地址空間。s0還是常量池中"helloworld”的引用提前,s1因為無法在編譯期確定吗货,所以是運行時創(chuàng)建的新對象"helloworld”的引用,s2因為有后半部分new String(”world”)所以也無法在編譯期確定狈网,所以也是一個新創(chuàng)建對象"helloworld”的引用宙搬。
繼續(xù)-編譯期無法確定
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println("===========test5============");
System.out.println(str3=="abcdef"); //false
執(zhí)行上述代碼,結(jié)果為:false拓哺。
分析:因為str3指向堆中的"abcdef"對象害淤,而"abcdef"是字符串池中的對象,所以結(jié)果為false拓售。JVM對String str="abc"對象放在常量池中是在編譯時做的窥摄,而String str3=str1+str2是在運行時刻才能知道的。new對象也是在運行時才做的础淤。而這段代碼總共創(chuàng)建了5個對象崭放,字符串池中兩個哨苛、堆中三個。+運算符會在堆中建立來兩個String對象币砂,這兩個對象的值分別是"abc"和"def"建峭,也就是說從字符串池中復(fù)制這兩個值,然后在堆中創(chuàng)建兩個對象决摧,然后再建立對象str3,然后將"abcdef"的堆地址賦給str3亿蒸。
步驟:
1)棧中開辟一塊中間存放引用str1,str1指向池中String常量"abc"掌桩。
2)棧中開辟一塊中間存放引用str2边锁,str2指向池中String常量"def"。
3)棧中開辟一塊中間存放引用str3波岛。
4)str1 + str2通過StringBuilder的最后一步toString()方法還原一個新的String對象"abcdef"茅坛,因此堆中開辟一塊空間存放此對象。
5)引用str3指向堆中(str1 + str2)所還原的新String對象则拷。
6)str3指向的對象在堆中贡蓖,而常量"abcdef"在池中,輸出為false煌茬。
編譯期優(yōu)化
String s0 = "a1";
String s1 = "a" + 1;
System.out.println("===========test6============");
System.out.println((s0 == s1)); //result = true
String s2 = "atrue";
String s3= "a" + "true";
System.out.println((s2 == s3)); //result = true
String s4 = "a3.4";
String s5 = "a" + 3.4;
System.out.println((s4 == s5)); //result = true
執(zhí)行上述代碼斥铺,結(jié)果為:true、true坛善、true晾蜘。
分析:在程序編譯期,JVM就將常量字符串的"+"連接優(yōu)化為連接后的值浑吟,拿"a" + 1來說,經(jīng)編譯器優(yōu)化后在class中就已經(jīng)是a1耗溜。在編譯期其字符串常量的值就確定下來组力,故上面程序最終的結(jié)果都為true。
編譯期無法確定
String s0 = "ab";
String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test7============");
System.out.println((s0 == s2)); //result = false
執(zhí)行上述代碼抖拴,結(jié)果為:false燎字。
分析:JVM對于字符串引用,由于在字符串的"+"連接中阿宅,有字符串引用存在候衍,而引用的值在程序編譯期是無法確定的,即"a" + s1無法被編譯器優(yōu)化洒放,只有在程序運行期來動態(tài)分配并將連接后的新地址賦給s2蛉鹿。所以上面程序的結(jié)果也就為false。
編譯期確定
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test9============");
System.out.println((s0 == s2)); //result = true
執(zhí)行上述代碼往湿,結(jié)果為:true妖异。
分析:s1字符串加了final修飾惋戏,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的字節(jié)碼流中他膳。所以此時的"a" + s1和"a" + "b"效果是一樣的响逢。故上面程序的結(jié)果為true。
編譯期無法確定
public void test10(){
String s0 = "ab";
final String s1 = getS1();
String s2 = "a" + s1;
System.out.println("===========test10============");
System.out.println((s0 == s2)); //result = false
}
private static String getS1() {
return "b";
}
執(zhí)行上述代碼棕孙,結(jié)果為:false舔亭。
分析:這里面雖然將s1用final修飾了,但是由于其賦值是通過方法調(diào)用返回的蟀俊,那么它的值只能在運行期間確定钦铺,因此s0和s2指向的不是同一個對象,故上面程序的結(jié)果為false欧漱。
三职抡、總結(jié)
1.String類初始化后是不可變的(immutable)
String使用private final char value[]來實現(xiàn)字符串的存儲,也就是說String對象創(chuàng)建之后误甚,就不能再修改此對象中存儲的字符串內(nèi)容缚甩,就是因為如此,才說String類型是不可變的(immutable)窑邦。程序員不能對已有的不可變對象進(jìn)行修改擅威。我們自己也可以創(chuàng)建不可變對象,只要在接口中不提供修改數(shù)據(jù)的方法就可以冈钦。
然而郊丛,String類對象確實有編輯字符串的功能,比如replace()瞧筛。這些編輯功能是通過創(chuàng)建一個新的對象來實現(xiàn)的厉熟,而不是對原有對象進(jìn)行修改。比如:
s = s.replace("World", "Universe");
上面對s.replace()的調(diào)用將創(chuàng)建一個新的字符串"Hello Universe!"较幌,并返回該對象的引用揍瑟。通過賦值,引用s將指向該新的字符串乍炉。如果沒有其他引用指向原有字符串"Hello World!"绢片,原字符串對象將被垃圾回收。
2.引用變量與對象
A aa;
這個語句聲明一個類A的引用變量aa[我們常常稱之為句柄]岛琼,而對象一般通過new創(chuàng)建底循。所以aa僅僅是一個引用變量,它不是對象槐瑞。
3.創(chuàng)建字符串的方式
創(chuàng)建字符串的方式歸納起來有兩類:
(1)使用""引號創(chuàng)建字符串;
(2)使用new關(guān)鍵字創(chuàng)建字符串熙涤。
結(jié)合上面例子,總結(jié)如下:
(1)單獨使用""引號創(chuàng)建的字符串都是常量,編譯期就已經(jīng)確定存儲到String Pool中;
(2)使用new String("")創(chuàng)建的對象會存儲到heap中,是運行期新創(chuàng)建的灭袁;
new創(chuàng)建字符串時首先查看池中是否有相同值的字符串猬错,如果有,則拷貝一份到堆中茸歧,然后返回堆中的地址倦炒;如果池中沒有,則在堆中創(chuàng)建一份软瞎,然后返回堆中的地址(注意逢唤,此時不需要從堆中復(fù)制到池中,否則涤浇,將使得堆中的字符串永遠(yuǎn)是池中的子集鳖藕,導(dǎo)致浪費池的空間)!
(3)使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經(jīng)確定存儲到String Pool中只锭;
(4)使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對象是運行期才創(chuàng)建的,存儲在heap中著恩;
4.使用String不一定創(chuàng)建對象
在執(zhí)行到雙引號包含字符串的語句時,如String a = "123"蜻展,JVM會先到常量池里查找喉誊,如果有的話返回常量池里的這個實例的引用,否則的話創(chuàng)建一個新實例并置入常量池里纵顾。所以伍茄,當(dāng)我們在使用諸如String str = "abc";的格式定義對象時施逾,總是想當(dāng)然地認(rèn)為敷矫,創(chuàng)建了String類的對象str。擔(dān)心陷阱汉额!對象可能并沒有被創(chuàng)建曹仗!而可能只是指向一個先前已經(jīng)創(chuàng)建的對象。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象蠕搜。
5.使用new String怎茫,一定創(chuàng)建對象
在執(zhí)行String a = new String("123")的時候,首先走常量池的路線取到一個實例的引用讥脐,然后在堆上創(chuàng)建一個新的String實例遭居,然后把實例引用賦值給a
6.關(guān)于String.intern()
intern方法使用:一個初始為空的字符串池啼器,它由類String獨自維護(hù)旬渠。當(dāng)調(diào)用 intern方法時,如果池已經(jīng)包含一個等于此String對象的字符串(用equals(oject)方法確定)端壳,則返回池中的字符串告丢。否則,將此String對象添加到池中损谦,并返回此String對象的引用岖免。
它遵循以下規(guī)則:對于任意兩個字符串 s 和 t岳颇,當(dāng)且僅當(dāng) s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true颅湘。
再補(bǔ)充介紹一點:存在于.class文件中的常量池话侧,在運行期間被jvm裝載,并且可以擴(kuò)充闯参。String的intern()方法就是擴(kuò)充常量池的一個方法瞻鹏;當(dāng)一個String實例str調(diào)用intern()方法時,java查找常量池中是否有相同unicode的字符串常量鹿寨,如果有新博,則返回其引用,如果沒有脚草,則在常量池中增加一個unicode等于str的字符串并返回它的引用赫悄。
關(guān)于String.intern()
String s0 = "kvill";
String s1 = new String("kvill");
String s2 = new String("kvill");
System.out.println("===========test11============");
System.out.println( s0 == s1 ); //false
System.out.println( "**********" );
s1.intern(); //雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
s2 = s2.intern(); //把常量池中"kvill"的引用賦給s2
System.out.println( s0 == s1); //flase
System.out.println( s0 == s1.intern() ); //true//說明s1.intern()返回的是常量池中"kvill"的引用
System.out.println( s0 == s2 ); //true
運行結(jié)果:false、false馏慨、true埂淮、true。
7.關(guān)于equals和==
(1)對于==熏纯,如果作用于基本數(shù)據(jù)類型的變量(byte,short,char,int,long,float,double,boolean )同诫,則直接比較其存儲的"值"是否相等;如果作用于引用類型的變量(String)樟澜,則比較的是所指向的對象的地址(即是否指向同一個對象)扭倾。
(2)equals方法是基類Object中的方法,因此對于所有的繼承于Object的類都會有該方法课舍。在Object類中敬特,equals方法是用來比較兩個對象的引用是否相等,即是否指向同一個對象毒费。
(3)對于equals方法丙唧,注意:equals方法不能作用于基本數(shù)據(jù)類型的變量。如果沒有對equals方法進(jìn)行重寫觅玻,則比較的是引用類型的變量所指向的對象的地址想际;而String類對equals方法進(jìn)行了重寫,用來比較指向的字符串對象所存儲的字符串是否相等溪厘。其他的一些類諸如Double胡本,Date,Integer等畸悬,都對equals方法進(jìn)行了重寫用來比較指向的對象所存儲的內(nèi)容是否相等侧甫。
8.String相關(guān)的+:
String中的 + 常用于字符串的連接。
String a = "aa";
String b = "bb";
String c = "xx" + "yy " + a + "zz" + "mm" + b;
System.out.println("===========test13============");
System.out.println(c);
顯然,通過字節(jié)碼我們可以得出如下幾點結(jié)論:
(1).String中使用 + 字符串連接符進(jìn)行字符串連接時披粟,連接操作最開始時如果都是字符串常量咒锻,編譯后將盡可能多的直接將字符串常量連接起來,形成新的字符串常量參與后續(xù)連接守屉;
(2).接下來的字符串連接是從左向右依次進(jìn)行惑艇,對于不同的字符串,首先以最左邊的字符串為參數(shù)創(chuàng)建StringBuilder對象拇泛,然后依次對右邊進(jìn)行append操作敦捧,最后將StringBuilder對象通過toString()方法轉(zhuǎn)換成String對象(注意:中間的多個字符串常量不會自動拼接)。
也就是說String c = "xx" + "yy " + a + "zz" + "mm" + b; 實質(zhì)上的實現(xiàn)過程是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();
由此得出結(jié)論:當(dāng)使用+進(jìn)行多個字符串連接時碰镜,實際上是產(chǎn)生了一個StringBuilder對象和一個String對象兢卵。
9.String的不可變性導(dǎo)致字符串變量使用+號的代價:
String s = "a" + "b" + "c";
String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = s1 + s2 + s3;
分析:變量s的創(chuàng)建等價于 String s = "abc"; 由上面例子可知編譯器進(jìn)行了優(yōu)化,這里只創(chuàng)建了一個對象绪颖。由上面的例子也可以知道s4不能在編譯期進(jìn)行優(yōu)化秽荤,其對象創(chuàng)建相當(dāng)于:
StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append(c);
String s = temp.toString();
由上面的分析結(jié)果,可就不難推斷出String 采用連接運算符(+)效率低下原因分析柠横,形如這樣的代碼:
public class Test {
public static void main(String args[]) {
String s = null;
for(int i = 0; i < 100; i++) {
s += "a";
}
}
}
每做一次 + 就產(chǎn)生個StringBuilder對象窃款,然后append后就扔掉。下次循環(huán)再到達(dá)時重新產(chǎn)生個StringBuilder對象牍氛,然后 append 字符串晨继,如此循環(huán)直至結(jié)束。 如果我們直接采用 StringBuilder 對象進(jìn)行 append 的話搬俊,我們可以節(jié)省 N - 1 次創(chuàng)建和銷毀對象的時間紊扬。所以對于在循環(huán)中要進(jìn)行字符串連接的應(yīng)用,一般都是用StringBuffer或StringBulider對象來進(jìn)行append操作唉擂。
10.String餐屎、StringBuffer、StringBuilder的區(qū)別
(1)可變與不可變:String是不可變字符串對象玩祟,StringBuilder和StringBuffer是可變字符串對象(其內(nèi)部的字符數(shù)組長度可變)腹缩。
(2)是否多線程安全:String中的對象是不可變的,也就可以理解為常量空扎,顯然線程安全藏鹊。StringBuffer 與 StringBuilder 中的方法和功能完全是等價的,只是StringBuffer 中的方法大都采用了synchronized 關(guān)鍵字進(jìn)行修飾转锈,因此是線程安全的盘寡,而 StringBuilder 沒有這個修飾,可以被認(rèn)為是非線程安全的黑忱。
(3)String宴抚、StringBuilder、StringBuffer三者的執(zhí)行效率:
StringBuilder > StringBuffer > String 當(dāng)然這個是相對的甫煞,不一定在所有情況下都是這樣菇曲。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此抚吠,這三個類是各有利弊常潮,應(yīng)當(dāng)根據(jù)不同的情況來進(jìn)行選擇使用:
當(dāng)字符串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式楷力;
當(dāng)字符串相加操作較多的情況下喊式,建議使用StringBuilder,如果采用了多線程萧朝,則使用StringBuffer岔留。
12.關(guān)于String str = new String("abc")創(chuàng)建了多少個對象?
這個問題在很多書籍上都有說到比如《Java程序員面試寶典》检柬,包括很多國內(nèi)大公司筆試面試題都會遇到献联,大部分網(wǎng)上流傳的以及一些面試書籍上都說是2個對象,這種說法是片面的何址。
首先必須弄清楚創(chuàng)建對象的含義里逆,創(chuàng)建是什么時候創(chuàng)建的?這段代碼在運行期間會創(chuàng)建2個對象么用爪?
在運行時原押,new只調(diào)用了一次,也就是說只創(chuàng)建了一個對象偎血。而這道題目讓人混淆的地方就是這里诸衔,這段代碼在運行期間確實只創(chuàng)建了一個對象,即在堆上創(chuàng)建了"abc"對象颇玷。而為什么大家都在說是2個對象呢署隘,這里面要澄清一個概念,該段代碼執(zhí)行過程和類的加載過程是有區(qū)別的亚隙。在類加載的過程中磁餐,確實在運行時常量池中創(chuàng)建了一個"abc"對象,而在代碼執(zhí)行過程中確實只創(chuàng)建了一個String對象阿弃。
因此诊霹,這個問題如果換成 String str = new String("abc")涉及到幾個String對象?合理的解釋是2個渣淳。
個人覺得在面試的時候如果遇到這個問題脾还,可以向面試官詢問清楚”是這段代碼執(zhí)行過程中創(chuàng)建了多少個對象還是涉及到多少個對象“再根據(jù)具體的來進(jìn)行回答。
13.字符串池的優(yōu)缺點:
字符串池的優(yōu)點就是避免了相同內(nèi)容的字符串的創(chuàng)建入愧,節(jié)省了內(nèi)存鄙漏,省去了創(chuàng)建相同字符串的時間嗤谚,同時提升了性能;另一方面怔蚌,字符串池的缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間巩步,不過其時間成本相比而言比較低。
四桦踊、綜合實例
package com.spring.test;
public class StringTest {
public static void main(String[] args) {
/**
* 情景一:字符串池
* JAVA虛擬機(jī)(JVM)中存在著一個字符串池椅野,其中保存著很多String對象;
* 并且可以被共享使用,因此它提高了效率籍胯。
* 由于String類是final的竟闪,它的值一經(jīng)創(chuàng)建就不可改變。
* 字符串池由String類維護(hù)杖狼,我們可以調(diào)用intern()方法來訪問字符串池炼蛤。
/
String s1 = "abc";
// 在字符串池創(chuàng)建了一個對象
String s2 = "abc";
// 字符串pool已經(jīng)存在對象“abc”(共享),所以創(chuàng)建0個對象,累計創(chuàng)建一個對象
System.out.println("s1 == s2 : "+(s1==s2));
// true 指向同一個對象蝶涩,
System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
// true 值相等
//------------------------------------------------------over
/*
* 情景二:關(guān)于new String("")
/
String s3 = new String("abc");
// 創(chuàng)建了兩個對象鲸湃,一個存放在字符串池中,一個存在與堆區(qū)中子寓;
// 還有一個對象引用s3存放在棧中
String s4 = new String("abc");
// 字符串池中已經(jīng)存在“abc”對象暗挑,所以只在堆中創(chuàng)建了一個對象
System.out.println("s3 == s4 : "+(s3==s4));
//false s3和s4棧區(qū)的地址不同,指向堆區(qū)的不同地址斜友;
System.out.println("s3.equals(s4) : "+(s3.equals(s4)));
//true s3和s4的值相同
System.out.println("s1 == s3 : "+(s1==s3));
//false 存放的地區(qū)多不同炸裆,一個棧區(qū),一個堆區(qū)
System.out.println("s1.equals(s3) : "+(s1.equals(s3)));
//true 值相同
//------------------------------------------------------over
/*
* 情景三:
* 由于常量的值在編譯的時候就被確定(優(yōu)化)了鲜屏。
* 在這里烹看,"ab"和"cd"都是常量,因此變量str3的值在編譯時就可以確定洛史。
* 這行代碼編譯后的效果等同于: String str3 = "abcd";
/
String str1 = "ab" + "cd"; //1個對象
String str11 = "abcd";
System.out.println("str1 = str11 : "+ (str1 == str11));
//------------------------------------------------------over
/*
* 情景四:
* 局部變量str2,str3存儲的是存儲兩個拘留字符串對象(intern字符串對象)的地址惯殊。
*
* 第三行代碼原理(str2+str3):
* 運行期JVM首先會在堆中創(chuàng)建一個StringBuilder類,
* 同時用str2指向的拘留字符串對象完成初始化也殖,
* 然后調(diào)用append方法完成對str3所指向的拘留字符串的合并土思,
* 接著調(diào)用StringBuilder的toString()方法在堆中創(chuàng)建一個String對象,
* 最后將剛生成的String對象的堆地址存放在局部變量str3中忆嗜。
*
* 而str5存儲的是字符串池中"abcd"所對應(yīng)的拘留字符串對象的地址己儒。
* str4與str5地址當(dāng)然不一樣了。
*
* 內(nèi)存中實際上有五個字符串對象:
* 三個拘留字符串對象捆毫、一個String對象和一個StringBuilder對象闪湾。
/
String str2 = "ab"; //1個對象
String str3 = "cd"; //1個對象
String str4 = str2+str3;
String str5 = "abcd";
System.out.println("str4 = str5 : " + (str4==str5)); // false
//------------------------------------------------------over
/*
* 情景五:
* JAVA編譯器對string + 基本類型/常量 是當(dāng)成常量表達(dá)式直接求值來優(yōu)化的。
* 運行期的兩個string相加绩卤,會產(chǎn)生新的對象的途样,存儲在堆(heap)中
*/
String str6 = "b";
String str7 = "a" + str6;
String str67 = "ab";
System.out.println("str7 = str67 : "+ (str7 == str67));
//str6為變量江醇,在運行期才會被解析。
final String str8 = "b";
String str9 = "a" + str8;
String str89 = "ab";
System.out.println("str9 = str89 : "+ (str9 == str89));
//str8為常量變量何暇,編譯期會被優(yōu)化
//------------------------------------------------------over
}
}
運行結(jié)果:
s1 == s2 : true
s1.equals(s2) : true
s3 == s4 : false
s3.equals(s4) : true
s1 == s3 : false
s1.equals(s3) : true
str1 = str11 : true
str4 = str5 : false
str7 = str67 : false
str9 = str89 : true