前言
RTFSC (Read the fucking source code )才是生活中最重要的费封。我們天天就是要讀懂別人的,理解別人的蒋譬,然后再模仿別人的属愤,最后才是創(chuàng)新自己的。人生大半的時間是在學習籽慢,所以我們一定要RTFSC
一浸遗、String類
想要了解一個類,最好的辦法就是看這個類的實現(xiàn)源代碼箱亿,來看一下String類的源碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
{ /** The value is used for character storage. */
private final char value[]; /** The offset is the first index of the storage that is used. */
private final int offset; /** The count is the number of characters in the String. */
private final int count; /** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
........
}
可以看出:
(1)String類是final類跛锌,即String類不能被繼承,并且它的成員變量都默認為final方法届惋。在Java中髓帽,被final修飾的類是不允許被繼承的,被final修飾變量盼樟,變量就變成了常量氢卡,只能被賦值一次;被final修飾方法晨缴,方法不能被重寫
(2)String類其實是通過char數(shù)組來保存字符串的译秦。
再繼續(xù)看String類的部分方法實現(xiàn):
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(this, beginIndex);
}
if (endIndex > length()) {
throw new StringIndexOutOfBoundsException(this, endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == length())) ? this
: fastSubstring(beginIndex, subLen);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
final int len = length();
for (int i = 0; i < len; ++i) {
if (charAt(i) == oldChar) {
return doReplace(oldChar, newChar);
}
}
}
return this;
}
從上面的方法可以看出,無論是sub操做击碗、還是replace操作都不是在原有的字符串上進行的筑悴,而是重新生成了一個新的字符串對象。也就是說進行這些操作后稍途,最原始的字符串并沒有被改變阁吝。
這里要記住一點:“String對象一旦被創(chuàng)建就固定不變,對String對象的任何改變都不影響原對象械拍,任何改變操作都是會生成新的對象”突勇。
二、字符串常量池
我們知道字符串的分配和其他普通對象分配一樣坷虑,是需要消耗一定的時間和空間的甲馋,而且字符串一般在我們程序中會大量使用。JVM為了提高性能和減少內存的開銷迄损,在實例化字符串的時候進行了一些優(yōu)化:使用字符串常量池定躏。每當我們創(chuàng)建字符串常量時,JVM會首先檢查字符串常量池,如果該字符串已經存在常量池中痊远,那么就直接返回常量池中的實例引用垮抗。如果字符串不存在常量池中,就會實例化該字符串并且將其放到常量池中碧聪。由于String字符串的不可變性我們可以十分肯定常量池中一定不存在兩個相同的字符串(這點對理解上面至關重要)冒版。
Java中的常量池,實際上分為兩種形態(tài):靜態(tài)常量池和運行時常量池矾削。
所謂靜態(tài)常量池壤玫,即.class文件中的常量池,class文件中的常量池不僅僅包含字符串(數(shù)字)字面量哼凯,還包含類欲间、方法的信息,占用class文件絕大部分空間断部。
而運行時常量池猎贴,則是jvm虛擬機在完成類裝載操作后,將class文件中的常量池載入到內存中蝴光,并保存在方法區(qū)中第股,我們常說的常量池瘪吏,就是指方法區(qū)中的運行時常量池棘伴。
分析一下如下程序:
String a = "hello";
String b = "hello";
a忌傻、b和字面上的hello都是指向JVM字符串常量池中的"hello"對象,他們指向同一個對象疆虚。
String c = new String("hello");
new關鍵字一定會產生一個對象hello(注意這個hello和上面的hello不同)苛败,同時這個對象是存儲在堆中。所以上面應該產生了兩個對象:保存在棧中的c和保存堆中hello径簿。但是在Java中根本就不存在兩個完全一模一樣的字符串對象罢屈。故堆中的hello應該是引用字符串常量池中hello。所以c篇亭、堆hello缠捌、池hello的關系應該是:c--->堆hello--->池hello。整個關系如下:
通過上面的圖我們可以非常清晰的認識他們之間的關系译蒂。所以我們修改內存中的值曼月,他變化的是所有。
總結:雖然a柔昼、b哑芹、c、hello是不同的對象岳锁,但是從String的內部結構我們是可以理解上面的绩衷。String c = new String("hello");雖然c的內容是創(chuàng)建在堆中,但是他的內部value還是指向JVM常量池的hello的value激率,它構造hello時所用的參數(shù)依然是hello字符串常量咳燕。
下面再來看幾個例子:
例子1:
/** * 采用字面值的方式賦值 */
public void test1(){
String str1="aaa";
String str2="aaa";
System.out.println("wfs::test1");
System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一個對象
}
執(zhí)行結果為:true。
分析:當執(zhí)行String str1="aaa"時乒躺,JVM首先會去字符串池中查找是否存在"aaa"這個對象招盲,如果不存在,則在字符串池中創(chuàng)建"aaa"這個對象嘉冒,然后將池中"aaa"這個對象的引用地址返回給字符串常量str1曹货,這樣str1會指向池中"aaa"這個字符串對象;如果存在讳推,則不創(chuàng)建任何對象顶籽,直接將池中"aaa"這個對象的地址返回,賦給字符串常量银觅。當創(chuàng)建字符串對象str2時礼饱,字符串池中已經存在"aaa"這個對象,直接把對象"aaa"的引用地址返回給str2究驴,這樣str2指向了池中"aaa"這個對象镊绪,也就是說str1和str2指向了同一個對象,因此輸出:true洒忧。
例子2:
/** * 采用new關鍵字新建一個字符串對象 */
public void test2(){
String str3=new String("aaa");
String str4=new String("aaa");
System.out.println("wfs::test2");
System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的對象
}
執(zhí)行結果為:false蝴韭。
分析: 采用new關鍵字新建一個字符串對象時,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"字符串對象堡牡。當執(zhí)行String str4=new String("aaa")時抒抬, 因為采用new關鍵字創(chuàng)建對象時,每次new出來的都是一個新的對象晤柄,也即是說引用str3和str4指向的是兩個不同的對象擦剑,因此語句System.out.println(str3 == str4)輸出:false。
例子3:
/** * 編譯期確定 */
public void test3(){
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println("wfs::test3");
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一個對象
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一個對象
}
執(zhí)行結果為:true、true惠勒。
分析:因為例子中的s0和s1中的"helloworld”都是字符串常量赚抡,它們在編譯期就被確定了,所以s0==s1為true纠屋;而"hello”和"world”也都是字符串常量涂臣,當一個字符串由多個字符串常量連接而成時,它自己肯定也是字符串常量售担,所以s2也同樣在編譯期就被解析為一個字符串常量赁遗,所以s2也是常量池中"helloworld”的一個引用。所以我們得出s0==s1==s2族铆。
例子4:
/** * 編譯期無法確定 */
public void test4(){
String s0="helloworld";
String s1=new String("helloworld");
String s2="hello" + new String("world");
System.out.println("wfs::test4");
System.out.println( s0==s1 ); //false
System.out.println( s0==s2 ); //false
System.out.println( s1==s2 ); //false
}
執(zhí)行結果為:false岩四、false、false哥攘。
分析:用new String() 創(chuàng)建的字符串不是常量炫乓,不能在編譯期就確定,所以new String() 創(chuàng)建的字符串不放入常量池中献丑,它們有自己的地址空間末捣。
s0還是常量池中"helloworld”的引用,s1因為無法在編譯期確定创橄,所以是運行時創(chuàng)建的新對象"helloworld”的引用箩做,s2因為有后半部分new String(”world”)所以也無法在編譯期確定,所以也是一個新創(chuàng)建對象"helloworld”的引用妥畏。
例子5:
/** * 繼續(xù)-編譯期無法確定 */
public void test5(){
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println("wfs::test5");
System.out.println(str3=="abcdef"); //false
}
執(zhí)行結果為:false邦邦。
分析:因為str3指向堆中的"abcdef"對象,而"abcdef"是字符串池中的對象醉蚁,所以結果為false燃辖。JVM對String str="abc"對象放在常量池中是在編譯時做的,而String str3=str1+str2是在運行時刻才能知道的网棍。new對象也是在運行時才做的黔龟。而這段代碼總共創(chuàng)建了5個對象,字符串池中兩個滥玷、堆中三個氏身。+運算符會在堆中建立兩個String對象,這兩個對象的值分別是"abc"和"def"惑畴,也就是說從字符串池中復制這兩個值蛋欣,然后在堆中創(chuàng)建兩個對象,然后再建立對象str3,然后將"abcdef"的堆地址賦給str3如贷。
步驟:
1)棧中開辟一塊地址空間存放引用str1陷虎,str1指向池中String常量"abc"到踏。
2)棧中開辟一塊地址空間存放引用str2,str2指向池中String常量"def"尚猿。
3)棧中開辟一塊地址空間存放引用str3夭禽。
4)而str1 + str2其實通過StringBuilder的append()方法,以及toString()方法還原一個新的String對象"abcdef"谊路,因此堆中開辟一塊空間存放此對象。
5)str3指向堆中(str1 + str2)所還原的新String對象菩彬。
6)str3指向的對象在堆中缠劝,而常量"abcdef"在池中,輸出為false骗灶。
例子6:
/** * 編譯期優(yōu)化 */
public void test6(){
String s0 = "a1";
String s1 = "a" + 1;
System.out.println("wfs::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í)行結果為:true惨恭、true、true耙旦。
分析:在程序編譯期脱羡,JVM就將常量字符串的"+"連接優(yōu)化為連接后的值,拿"a" + 1來說免都,經編譯器優(yōu)化后在class中就已經是a1锉罐。在編譯期其字符串常量的值就確定下來,故上面程序最終的結果都為true绕娘。
例子7:
/** * 編譯期無法確定 */
public void test7(){
String s0 = "ab";
String s1 = "b";
String s2 = "a" + s1;
System.out.println("wfs::test7");
System.out.println((s0 == s2)); //result = false
}
執(zhí)行結果為:false脓规。
分析:JVM對于字符串引用,由于在字符串的"+"連接中险领,有字符串引用存在侨舆,而引用的值在程序編譯期是無法確定的(該引用的值是可以改變的),即"a" + s1無法被編譯器優(yōu)化绢陌,只有在程序運行期來動態(tài)分配并將連接后的新地址賦給s2挨下。所以上面程序的結果也就為false。
例子8:
/** * 比較字符串常量的“+”和字符串引用的“+”的區(qū)別 */
public void test8(){
String test="javalanguagespecification";
String str="java";
String str1="language";
String str2="specification";
System.out.println("wfs::test8");
System.out.println(test == "java" + "language" + "specification");
System.out.println(test == str + str1 + str2);
}
執(zhí)行結果為:true脐湾、false臭笆。
分析:為什么出現(xiàn)上面的結果呢?這是因為秤掌,字符串字面量拼接操作是在Java編譯器編譯期間就執(zhí)行了耗啦,也就是說編譯器編譯時,直接把"java"机杜、"language"和"specification"這三個字面量進行"+"操作得到一個"javalanguagespecification" 常量帜讲,并且直接將這個常量放入字符串池中,這樣做實際上是一種優(yōu)化椒拗,將3個字面量合成一個似将,避免了創(chuàng)建多余的字符串對象获黔。而字符串引用的"+"運算是在Java運行期間執(zhí)行的,即str + str2 + str3在程序執(zhí)行期間才會進行計算在验,它會在堆內存中重新創(chuàng)建一個拼接后的字符串對象玷氏。總結來說就是:字面量"+"拼接是在編譯期間進行的腋舌,拼接后的字符串存放在字符串池中盏触;而字符串引用的"+"拼接運算實在運行時進行的,新創(chuàng)建的字符串存放在堆中块饺。
對于直接相加字符串赞辩,效率很高,因為在編譯器便確定了它的值授艰,也就是說形如"I"+"love"+"java"; 的字符串相加辨嗽,在編譯期間便被優(yōu)化成了"Ilovejava"。對于間接相加(即包含字符串引用)淮腾,形如s1+s2+s3; 效率要比直接相加低糟需,因為在編譯器不會對引用變量進行優(yōu)化。
例子9:
/** * 編譯期確定 */
public void test9(){
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println("wfs::test9");
System.out.println((s0 == s2)); //result = true
}
執(zhí)行結果為:true谷朝。
分析:和例子7中唯一不同的是s1字符串加了final修飾洲押,對于final修飾的變量,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中圆凰。所以此時的"a" + s1和"a" + "b"效果是一樣的诅诱。故上面程序的結果為true。
例子10:
/** * 編譯期無法確定 */
public void test10(){
String s0 = "ab";
final String s1 = getStr();
String s2 = "a" + s1;
System.out.println("wfs::test10");
System.out.println((s0 == s2)); //result = false
}
private static String getStr()
{
return "b";
}
執(zhí)行結果為:false送朱。
分析:這里面雖然將s1用final修飾了娘荡,但是由于其賦值是通過方法調用返回的,那么它的值只能在運行期間確定驶沼,因此s0和s2指向的不是同一個對象炮沐,故上面程序的結果為false。
三回怜、總結
1.String類初始化后是不可變的(immutable)
String使用private final char value[]
來實現(xiàn)字符串的存儲大年,也就是說String對象創(chuàng)建之后,就不能再修改此對象中存儲的字符串內容玉雾,就是因為如此翔试,才說String類型是不可變的(immutable)。程序員不能對已有的不可變對象進行修改复旬。我們自己也可以創(chuàng)建不可變對象垦缅,只要在接口中不提供修改數(shù)據(jù)的方法就可以。
然而驹碍,String類對象確實有編輯字符串的功能壁涎,比如replace()凡恍。這些編輯功能是通過創(chuàng)建一個新的對象來實現(xiàn)的,而不是對原有對象進行修改怔球。比如:
String s = "Hello World";
s = s.replace("World", "Universe");
上面對s.replace()的調用將創(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關鍵字創(chuàng)建字符串碘赖。
結合上面例子驾荣,總結如下:
(1)單獨使用""引號創(chuàng)建的字符串都是常量,編譯期就已經確定存儲到String Pool中;
(2)使用new String("")創(chuàng)建的對象會存儲到heap中,是運行期新創(chuàng)建的普泡;
new創(chuàng)建字符串時首先查看池中是否有相同值的字符串播掷,如果有,則拷貝一份到堆中撼班,然后返回堆中的地址歧匈;如果池中沒有,則在堆中創(chuàng)建一份砰嘁,然后返回堆中的地址(注意件炉,此時不需要從堆中復制到池中,否則矮湘,將使得堆中的字符串永遠是池中的子集斟冕,導致浪費池的空間)!
(3)使用只包含常量的字符串連接符如"aa" + "aa"創(chuàng)建的也是常量,編譯期就能確定,已經確定存儲到String Pool中缅阳;
(4)使用包含變量的字符串連接符如"aa" + s1創(chuàng)建的對象是運行期才創(chuàng)建的,存儲在heap中磕蛇;
4.使用String不一定創(chuàng)建對象
在執(zhí)行到雙引號包含字符串的語句時,如String a = "123"十办,JVM會先到常量池里查找秀撇,如果有的話返回常量池里的這個實例的引用,否則的話創(chuàng)建一個新實例并置入常量池里向族。所以呵燕,當我們在使用諸如String str = "abc";的格式定義對象時件相,總是想當然地認為虏等,創(chuàng)建了String類的對象str弄唧。擔心陷阱!對象可能并沒有被創(chuàng)建霍衫!而可能只是指向一個先前已經創(chuàng)建的對象候引。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象。
5.使用new String敦跌,一定創(chuàng)建對象
在執(zhí)行String a = new String("123")的時候澄干,首先走常量池的路線取到一個實例的引用,然后在堆上創(chuàng)建一個新的String實例柠傍,走以下構造函數(shù)給value屬性賦值麸俘,然后把實例引用賦值給a:
public String(String original)
{
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size)
{ // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else { // The array representing the String is the same // size as the String, so no point in making a copy.
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}
從中我們可以看到,雖然是新創(chuàng)建了一個String的實例惧笛,但是value是等于常量池中的實例的value从媚,即是說沒有new一個新的字符數(shù)組來存放"123"。
6.String.intern()
intern方法是一個nativie方法患整。解釋:一個初始為空的字符串池拜效,它由類String獨自維護。當調用 intern方法時各谚,如果池已經包含一個等于此String對象的字符串(用equals(oject)方法確定)紧憾,則返回池中的字符串。否則昌渤,將此String對象添加到池中赴穗,并返回此String對象的引用。
它遵循以下規(guī)則:對于任意兩個字符串 s 和 t膀息,當且僅當 s.equals(t) 為 true 時般眉,s.intern() == t.intern() 才為 true。
7.關于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方法進行重寫裕循,則比較的是引用類型的變量所指向的對象的地址臣嚣;而String類對equals方法進行了重寫净刮,用來比較指向的字符串對象所存儲的字符串是否相等。其他的一些類諸如Double硅则,Date淹父,Integer等,都對equals方法進行了重寫用來比較指向的對象所存儲的內容是否相等怎虫。
/** * 關于equals和== */
public void test12(){
String s1="hello";
String s2="hello";
String s3=new String("hello");
System.out.println("wfs::test12");
System.out.println( s1 == s2); //true,表示s1和s2指向同一對象暑认,它們都指向常量池中的"hello"對象 //flase,表示s1和s3的地址不同,即它們分別指向的是不同的對象,s1指向常量池中的地址大审,s3指向堆中的地址
System.out.println( s1 == s3);
System.out.println( s1.equals(s3)); //true,表示s1和s3所指向對象的內容相同
}
8.String相關的+:
String中的 + 常用于字符串的連接蘸际。看下面一個簡單的例子:
/** * String相關的+ */
public void test13(){
String a = "aa";
String b = "bb";
String c = a+"cc" + "dd " + "ee" + "ff" + b;
System.out.println("wfs::test13");
System.out.println(c);
}
String c = "xx" + "yy " + a + "zz" + "mm" + b; 實質上的實現(xiàn)過程是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();
由此得出結論:當使用+進行多個字符串連接時徒扶,實際上是產生了一個StringBuilder對象和一個String對象粮彤。
9.String的不可變性導致字符串變量使用+號的代價:
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"; 由上面例子可知編譯器進行了優(yōu)化,這里只創(chuàng)建了一個對象姜骡。由上面的例子也可以知道s4不能在編譯期進行優(yōu)化导坟,其對象創(chuàng)建相當于:
StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append(c);
String s = temp.toString();
由上面的分析結果,可就不難推斷出String 采用連接運算符(+)效率低下原因分析溶浴,形如這樣的代碼:
public class Test { public static void main(String args[]) {
String s = null; for(int i = 0; i < 100; i++) {
s += "a";
}
}
}
每做一次 + 就產生個StringBuilder對象乍迄,然后append后就扔掉管引。下次循環(huán)再到達時重新產生個StringBuilder對象士败,然后 append 字符串,如此循環(huán)直至結束褥伴。 如果我們直接采用 StringBuilder 對象進行 append 的話谅将,我們可以節(jié)省 N - 1 次創(chuàng)建和銷毀對象的時間。所以對于在循環(huán)中要進行字符串連接的應用重慢,一般都是用StringBuffer或StringBulider對象來進行append操作饥臂。
10.String、StringBuffer似踱、StringBuilder的區(qū)別
(1)可變與不可變:String是不可變字符串對象隅熙,StringBuilder和StringBuffer是可變字符串對象(其內部的字符數(shù)組長度可變)。
(2)是否多線程安全:String中的對象是不可變的核芽,也就可以理解為常量囚戚,顯然線程安全。StringBuffer 與 StringBuilder 中的方法和功能完全是等價的轧简,只是StringBuffer 中的方法大都采用了synchronized 關鍵字進行修飾驰坊,因此是線程安全的,而 StringBuilder 沒有這個修飾哮独,可以被認為是非線程安全的拳芙。
(3)String察藐、StringBuilder、StringBuffer三者的執(zhí)行效率:
StringBuilder > StringBuffer > String 當然這個是相對的舟扎,不一定在所有情況下都是這樣分飞。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此浆竭,這三個類是各有利弊浸须,應當根據(jù)不同的情況來進行選擇使用:
當字符串相加操作或者改動較少的情況下,建議使用 String str="hello"這種形式邦泄;
當字符串相加操作較多的情況下删窒,建議使用StringBuilder,如果采用了多線程顺囊,則使用StringBuffer肌索。
11.String str = new String("abc")到底創(chuàng)建了多少個對象?
用javap -c反編譯即可得到JVM執(zhí)行的字節(jié)碼內容:
很顯然特碳,new只調用了一次诚亚,也就是說只創(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ù)具體的來進行回答蜂大。
12.字符串常量池:
字符串常量池的優(yōu)點就是避免了相同內容的字符串的創(chuàng)建,節(jié)省了內存蝶怔,省去了創(chuàng)建相同字符串的時間奶浦,同時提升了性能;另一方面添谊,字符串池的缺點就是犧牲了JVM在常量池中遍歷對象所需要的時間财喳,不過其時間成本相比而言比較低。
本文參考書籍:《Java編程思想第四版》,《Java核心技術 卷I》
本文參考鏈接: 淺析java內存管理機制
如有錯誤請指出