首先來(lái)看一道題。
題目描述
問(wèn)下面兩種賦值方式有何區(qū)別譬巫?
public class Demo {
public static void main(String[] args) {
String s = "1" + "2" + "3";
String s1 = "1";
String s2 = s1 + "2";
String s3 = s2 + "3";
System.out.println(s);
System.out.println(s3);
}
}
分析解答
從表面其實(shí)看不出什么咖楣,我們可以通過(guò)Class文件反編譯成的字節(jié)碼(Byte Code)來(lái)分析。
如果你在使用IDEA芦昔,請(qǐng)先在IDEA中安裝ASMified Bytecode Online插件诱贿,安裝詳細(xì)教程->推薦幾個(gè)可以提高程序員生存技能的效率軟件,如果是其他集成環(huán)境咕缎,請(qǐng)自行Google安裝插件教程珠十。
ASMified Bytecode Online插件作用:用于將Class文件反編譯成字節(jié)碼(Byte Code)形式。將上面代碼在IDEA中運(yùn)行后凭豪,生成的字節(jié)碼(Byte Code)如下圖所示:
...
public static void main(String[] a) {
ldc "123"
//astore 1
ldc "1"
//astore 2
_new 'java/lang/StringBuilder'
//dup
//INVOKESPECIAL java/lang/StringBuilder.<init> ()V
//aload 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ldc "2"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
//astore 3
_new 'java/lang/StringBuilder'
//dup
//INVOKESPECIAL java/lang/StringBuilder.<init> ()V
//aload 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ldc "3"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
....
}
看不懂焙蹭,沒(méi)關(guān)系!你只需要知道幾個(gè)指令就能理解了嫂伞。根據(jù)《深入理解Java虛擬機(jī)(第二版)》---周志明著的第六章知識(shí)可知:
- ldc指令:將一個(gè)常量加載到操作數(shù)棧孔厉。
- _new指令:實(shí)例化對(duì)象
- invokevirtual指令:用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派(虛方法分派)帖努,這也是Java語(yǔ)言中最常見(jiàn)的方法分派方式撰豺。
我們根據(jù)字節(jié)碼順序來(lái)看:
// "1"+"2"+"3"被JVM轉(zhuǎn)換成了字符串常量"123"存儲(chǔ)到操作數(shù)棧
String s = "1" + "2" + "3";
跳過(guò)astore、dup等指令(不是本節(jié)重點(diǎn))
/**
* JVM將"1"存儲(chǔ)到操作數(shù)棧
* JVM用_new指令實(shí)例化一個(gè)StringBuilder對(duì)象,調(diào)用append()方法連接"1"
* JVM將"2"存儲(chǔ)到操作數(shù)棧
* 調(diào)用append()方法連接"2"
* 調(diào)用toString()轉(zhuǎn)換成String類型
* JVM_new指令再實(shí)例化一個(gè)StringBuilder對(duì)象拼余,調(diào)用append()方法連接"12"
* JVM將"3"存儲(chǔ)到操作數(shù)棧
* 調(diào)用append()方法連接"3"
* 調(diào)用toString()轉(zhuǎn)換成String類型
* */
String s1 = "1";
String s2 = s1 + "2";
String s3 = s2 + "3";
當(dāng)時(shí)用使用+
操作符連接字符串時(shí)污桦,為什么兩者有無(wú)字符串對(duì)象就有區(qū)別呢?
是因?yàn)槿绻怀霈F(xiàn)字符串引用匙监,字符串常量的值在編譯期時(shí)就可以確定下來(lái)凡橱,所以不會(huì)使用到StringBuilder;如果出現(xiàn)字符串引用亭姥,JVM不能將字符串引用和字符串常量直接連接稼钩,所以將在運(yùn)行期間動(dòng)態(tài)生成StringBuilder對(duì)象,讓它去實(shí)現(xiàn)連接致份。
說(shuō)了StringBuilder变抽,就不能不提StringBuffer础拨,兩者最大的區(qū)別是氮块,前者線程不安全的,后者是線程安全的诡宗。不能一看是線程不安全就覺(jué)得不好滔蝉,其實(shí)線程不安全比線程安全效率高,再者塔沃,因?yàn)槲覀儗?xiě)的一些代碼不用線程安全這樣多此一舉蝠引。
特別注意!在循環(huán)中連接字符串時(shí),最好不要出現(xiàn)字符串引用螃概,因?yàn)槊看窝h(huán)都會(huì)新建StringBuilder矫夯,即使Java有垃圾回收機(jī)制,這樣也很浪費(fèi)資源吊洼。這時(shí)候就可以用StringBuilder來(lái)連接字符串训貌。
public class Demo {
public static void main(String[] args) {
//我們這樣寫(xiě)
StringBuilder builder = new StringBuilder();
for (int i = 1; i < 10; i++) {
builder.append(i);
}
System.out.println(builder.toString());
//而不是這樣寫(xiě)
String s = "";
for (int i = 1; i < 10; i++) {
s = s + i;
}
System.out.println(s);
}
}
總結(jié)
如果此博文謬誤,還望各位路過(guò)的朋友指正!