String 对象是不可变的。String类中的每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
重载“+”与StringBuilder:
String对象具有只读属性,所以指向它的任何引用都不可能改变它的值。因此,也就不会对其他的引用有什么影响。
不可变性会带来一定的效率问题。为String对象重载的“+”操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于String的“+”与“+=”是java中仅有的两个重载过的操作符,而JAVA不允许程序员吃那个在任何操作符)
操作符“+”可以用来连接String。
public class StringTest {
public static void main(String[] args) {
String mango="mango";
String s="a"+mango+"b";
System.out.println(s);
}
}
通过jdk自带的javap可以反编译以上代码,命令如下:
javap -c StringTest
得到以下代码:
Compiled from "StringTest.java"
public class StringTest {
public StringTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String mango
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String a
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String b
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_2
28: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_2
32: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
}
从绿色背景的代码段中可以看到java.lang.StringBuilder类的引入。虽然我们没有使用这个类,但是编译器自主的使用了它,因为它更高效。
在上面的例子里面,编译器创建了一个StringBuilder对象,用以构造最终的String,并为每个字符串调用一次append()方法,共3次,最后调用toString()生成结果,并存储为s。
虽然这样看来编译器会自动优化性能,但是在写两个一下方法:
public String implicit(String[] fields){
String result="";
for (int i = 0; i < fields.length; i++) {
result+=fields[i];
}
return result;
}
public String expicit(String[] fields){
StringBuilder result=new StringBuilder();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
再次运行javap -c StringTest:
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #11 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38(对堆栈中的操作数进行“大于或等于的整数比较运算”循环结束时跳到38行)
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5 (返回循环体的起始点 第五行)
38: aload_2
39: areturn
从循环开始到结束,可以看到StringBuilder是在循环体内构造的,这意味着没循环一次就会创建一个新的StringBuilder对象
public java.lang.String expicit(java.lang.String[]);
Code:
0: new #3 // class java/lang/StringBuilder
3: dup
4: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
而第二个方法中可以看到,代码更简短,并且只生成了一个StringBuilder对象。显式的创建StringBuilder还允许预先指定大小,如果是预先知道字符串的大小大概有多少,那就可以预先指定其大小可以避免多次重新分配缓冲
因此,如果为一个类编写toString()方法时,如果字符串比较简单就可以信赖编辑器,如果是需要循环,那么还是最好自己创建一个StringBuilder对象。
如果判断不来哪个更好,随时可以用javap来分析你的程序。
StringBuilder提供的方法:
insert()
repleace()
substring()
reverse()
append()
toString()
delete()
StringBuilder是java SE5开始引入的,之前是StringBuffer,后者是线程安全的。
(看书时的记录)