英语原文共 51 页,剩余内容已隐藏,支付完成后下载完整资料
字符串
可以证明,字符串操作是计算机程序设计中最常见的行为。
尤其是在java大展拳脚的Web系统中更是如此。在本章中,我们将深入学习在Java语言中应用最广泛的String类,并研究与之相关的类与工具。
不可变String
String对象是不可变的。查看JDK文档就会发现。String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以及包含修改后的字符串内容。而最初的String对象则丝毫未动。
看看以下的代码:
//: strings/Immutable.java
import static net.mindview.util.Print.*;
public class Immutable {
public static String upcase(String s) { return s.toUpperCase();
}
public static void main(String[] args) { String q = 'howdy';
print(q); // howdy String qq = upcase(q); print(qq); // HOWDY print(q); // howdy
}
} /* Output: howdy
HOWDY
howdy
*///:~
当把q传给upcase()方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
回到upcase()的定义,传入其中的引用有了名字s。只有upcase()运行的时候,局部引用s才存在。一旦upcase()运行结束,s就消失了。当然,upcase()的返回值,其只是在最终结果的引用。这可以说明,upcase()返回的引用已经指向了一个新的对象,而原本的q则还在原地。
String的这种行为方式其实正是我们想要的,例如:
String s = 'asdf';
String x = Immutable.upcase(s);
难道你真的希望upcase()改变其参数嘛?对于一个方法而言。参数是为方法提供信息的,而不是想让方法改变自己的。在阅读这段代码时,读者自然就会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。
重载“ ”与StringBuilder
String对象是不变的,你可以给一个String对象加任意多的别名。因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值。因此,也就不会对其他的引用有什么影响。
不可变性会带来一定的效率问题。为String对象重载的“ ”操作符就是一个例子。重载的意思是,一个操作符在应用于特定的类时,被赋予了特殊的意义(用于String的“ ”与“ =”是Java中仅有的两个重载过的操作符而Java并不允许程序员重载任何操作符)。
操作符“ ”可以用来连接String:
//: strings/Concatenation.java
public class Concatenation {
public static void main(String[] args) {
String mango = 'mango';
String s = 'abc' mango 'def' 47; System.out.println(s);
}
} /* Output: abcmangodef47
*///:~
可以想象一下,这段代码可能是这样工作的:String可能有一个append()方法,它会生成一个新的String对象,以包含“abc”与mango连接后的字符串。然后,该对象再与“def”相连,生成另一个新的String对象,依此类推。
这种工作方式当然可以,但是为了生成最终的String,此方式会产生一大堆需要垃圾回收的中间对象。我猜测,Java设计师一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它动起来,否则你无法真正了解它会有什么问题),然后他们发现其功能性非常差。
想看看以上代码到底是如何工作的吗,可以用JDK自带的工具javap来反编译以上代码。命令如下:
javap -c Concatenation
这里的-c标志表示生成JVM字节码。我剔除掉了不感兴趣的部分,然后做了一点点修改,于是有了以下的字节码:
public static void main(java.lang.String[]); Code:
Stack=2, Locals=3, Args_size=1 0: ldc #2; //String mango
2: astore_1
3: new #3; //class StringBuilder
6: dup
7: invokespecial #4; //StringBuilder.'lt;initgt;':()
10: ldc #5; // String abc
12 invokevirtual #6; //StringBuilder.append:(String)
- aload_1
- invokevirtual #6; //StringBuilder.append:(String)
19 ldc #7; //String def
21 invokevirtual #6; //StringBuilder.append:(String)
24 bipush 47
26 invokevirtual #8; //StringBuilder.append:(I)
29 invokevirtual #9; //StringBuilder.toString:()
- astore_2
- getstatic #10; //Field System.out:PrintStream;
- aload_2
- invokevirtual #11; // PrintStream.println:(String)
40 return
如果你有汇编语言的经验,以上代码一定看着很眼熟,其中的dup与invokevirtural语句相当于java虚拟机上的汇编语句。即使你完全不了解汇编语言也无须担心。需要注意的重点是:编译器自动引入了java.lang.StringBuilder类。虽然在我们的源代码中并没有使用StringBuilder类,但是编译器却自作主张的使用了它,因为它更高效。
在这个例子中,编译器创建了一个StringBuilder对象,用以构造最终的String,并为每个字符串调用一次StringBuilder的append()方法,总计4次。最后调用toString()生成结果,并存为s(使用的命令为astore_2)。
现在,也许你会觉得可以随意使用String对象,反正编译器会为你自动地优化性能,可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。下面的程序采用两种方式生成一个String:方法一使用了多个String对象;方法二在代码中使用了StringBuilder。
//: strings/WhitherStringBuilder.java public class WhitherStringBuilder {
public String implicit(String[] fields) { String result = '';
for(int i = 0; i lt; fields.length; i ) result = fields[i];
return result;
}
public String explicit(String[] fields) { StringBuilder result = new StringBuilder(); for(int i = 0; i lt; fields.length; i )
result.append(fields[i]); return result.toString();
}
} ///:~
现在运行javap -c WitherStringBuilder,可以看到两个方法对应的(简化过的)字节码。首先是implicit()方法:
public 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 StringBuilder
14: dup
15: invokespecial #4; // StringBuilder.”lt;initgt;”:()
18: aload_2
19: invokevirtual #5; // StringBuilder.append:()
22: aload_1
- iload_3
- aaload
25: invokevirtual #5; // StringBuilder.append:()
28: invokevirtual #6; // StringBuiIder.toString:()
31: astore_2
32: iinc 3, 1
35: goto 5
38: aload_2
39 areturn
注意从第8行到第35行构成了一个循环体。第8行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳刀第38行。第35行:返回循环体的起始点(第5行)。要注意的重点是:StringBuilder实在循环之内构造的,这意味着每经过循环一次,就会创建一个新的StringBuilder对象。
下面是explicit()方法对应的字节码:
public java.lang.String explicit(java.lang.String[]); Code:
0: new #3; //class StringBuilder
3: dup
4: invokespecial #4; // StringBuilder.”lt;initgt;”:()
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; // StringBuilder.append:()
23 pop
24: iinc 3,1
27: goto 10
30: aload_2
31: invokevirtual #6; // StringBuiIder.toString:()
34: areturn
可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个StringBuilder对象。显式地创建StringBUilder还允许你预先指定其大小。如果你已经知道最终字符串大概有多长,那预先指定StringBuilder的大小可以避免多次重新分配缓冲。
因此,当你为一个类编写toString()方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象,用它来构造最终的结果。请参考以下示例:
//: strings/UsingStringBuilder.java import java.util.*;
public class UsingStringBuilder {
public static Random rand = new Random(47); public String toString() {
StringBuilder result = new StringBuilder('['); for(int i = 0; i lt; 25; i ) {
result.append(rand.nextInt(100)); result.append(
全文共23597字,剩余内容已隐藏,支付完成后下载完整资料
资料编号:[11571],资料为PDF文档或Word文档,PDF文档可免费转换为Word
以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。