- 可以证明,字符串操作是计算机程序设计中最常见的行为。
不可变String
- String 对象时不可变的。查看JDK文档你就会发现,String 类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。
public class Immutable {
static String upcase(String s){
return s.toUpperCase();
}
public static void main(String[] args) {
String q="howdy";
System.out.println(q);
String qq=upcase(q);
System.out.println(qq);
System.out.println(q);
}
}
//运行结果为
howdy
HOWDY
howdy
- 当q 传给upcase() 方法时,实际传递的是引用的一个拷贝。其实,每当把String 对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
- 回到upcase() 的定义,传入其中的引用有了名字s,只有 upcase() 运行的时候,局部引用 s才存在。一旦 upcase() 运行结束,s就消失了。当然了,upcase() 的返回值,其实只有最终结果的引用。
- 这足以说明,upcase() 返回的引用已经指向了一个新的对象,而原本的q则还在原地。
String s="asdf";
String x= Immutable.upcase(s);
- 难道你真的希望 upcase() 改变其参数吗? 对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。
- 在阅读这段代码时,读者自然就会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写与阅读。
重载 "+" 与 StringBuilder
- String 对象时不可变的,你可以给一个String对象加任意多的别名。因为String 对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也不会对其他的引用有什么影响。
- 不可变性会带来一定的效率问题。为 String 对象重载的 "+" 操作符就是一个例子。重载的意思是,一个操作符在应用于特定的类时,被赋予了特殊的意义(用于 String 的 "+" 与 "+=" 是Java 中仅有的两个重载过的操作符,而Java并不允许程序员重载任何操作符)。
- 操作符 "+" 可以用来连接String:(如果不会反编译请参考 https://blog.csdn.net/qq_40646143/article/details/105833724)
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 abc
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 def
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: bipush 47
26: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
29: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
32: astore_2
33: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_2
37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
}
- 如果你有汇编语言的经验,以上代码一定看着眼熟,其中的 dup 与 invokevirtural 语句相当于 Java 虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心,需要注意的重点是: 编译器自动引用了 java.lang.StringBuilder 类。
- 虽然我们再源代码中并没有使用 StringBuilder ,但是编译器却自作主张地使用了它,因为它更高效。
- 在这个例子中,编译器创建了一个StringBuilder对象,用以构造最终的String,并未每个字符串调用一次StringBuilder的append()方法,总计四次。最后调用toString()生成结果,并存为 s(使用的命令为 astore_2)。
- 现在,也许你会觉得可以随意使用String 对象,反正编译器会为你自动地优化性能。可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。如下
public class WhitherStringBuilder {
String implicit(String[] fields){
String result="";
for (int i = 0; i < fields.length; i++) {
result +=fields[i];
}
return result;
}
String explicit(String [] fields){
StringBuilder sb=new StringBuilder();
for (int i = 0; i < fields.length; i++) {
sb.append(fields[i]);
}
return sb.toString();
}
}
- implicit() 使用javap反编译后如下
java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #2 // String
2: astore_2
3: iconst_0
4: istore_3
5: iload_3
6: aload_1
7: arraylength
8: if_icmpge 38
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
18: aload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: aaload
25: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39: areturn
- 注意: 从第8行到35行构成了一个循环体。第8行: 对堆栈中的操作数进行 大于或等于的整数比较运算,循环结束时跳到第38行。第35行: 返回循环体的起始点(第5行)。
- 需要注意的重点是: StringBuilder 是在循环之内构造的,这意味着每经过循环一次,就会创建一个新的StringBuilder对象。
- 接下来是 explicit() 方法对应的反编译字节码。
java.lang.String explicit(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 #5 // 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 #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
- 可以看到,不仅循环部分的代码更简短,更简单,而且它只生成了一个StringBuilder 对象。
- 显示地创建StringBuilder 还允许为其指定大小。如