按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
请观察下述代码:
//: Stringer。java
public class Stringer {
static String upcase(String s) {
return s。toUpperCase();
}
public static void main(String'' args) {
String q = new String(〃howdy〃);
System。out。println(q); // howdy
String qq = upcase(q);
System。out。println(qq); // HOWDY
System。out。println(q); // howdy
}
} ///:~
q 传递进入 upcase()时,它实际是q 的句柄的一个副本。该句柄连接的对象实际只在一个统一的物理位置
处。句柄四处传递的时候,它的句柄会得到复制。
若观察对upcase() 的定义,会发现传递进入的句柄有一个名字 s,而且该名字只有在upcase()执行期间才会
存在。upcase()完成后,本地句柄 s 便会消失,而 upcase()返回结果——还是原来那个字串,只是所有字符
都变成了大写。当然,它返回的实际是结果的一个句柄。但它返回的句柄最终是为一个新对象的,同时原来
的q 并未发生变化。所有这些是如何发生的呢?
1。 隐式常数
若使用下述语句:
String s = 〃asdf〃;
String x = Stringer。upcase(s);
那么真的希望upcase()方法改变自变量或者参数吗?我们通常是不愿意的,因为作为提供给方法的一种信
息,自变量一般是拿给代码的读者看的,而不是让他们修改。这是一个相当重要的保证,因为它使代码更易
编写和理解。
为了在C++中实现这一保证,需要一个特殊关键字的帮助:const。利用这个关键字,程序员可以保证一个句
柄(C++叫“指针”或者“引用”)不会被用来修改原始的对象。但这样一来,C++程序员需要用心记住在所
有地方都使用const。这显然易使人混淆,也不容易记住。
2。 覆盖〃+〃和StringBuffer
利用前面提到的技术,String 类的对象被设计成 “不可变”。若查阅联机文档中关于String 类的内容(本
章稍后还要总结它),就会发现类中能够修改 String 的每个方法实际都创建和返回了一个崭新的String 对
象,新对象里包含了修改过的信息——原来的 String 是原封未动的。因此,Java 里没有与C++的const 对应
的特性可用来让编译器支持对象的不可变能力。若想获得这一能力,可以自行设置,就象String 那样。
由于String 对象是不可变的,所以能够根据情况对一个特定的 String 进行多次别名处理。因为它是只读
的,所以一个句柄不可能会改变一些会影响其他句柄的东西。因此,只读对象可以很好地解决别名问题。
通过修改产生对象的一个崭新版本,似乎可以解决修改对象时的所有问题,就象 String 那样。但对某些操作
来讲,这种方法的效率并不高。一个典型的例子便是为String 对象覆盖的运算符“+”。“覆盖”意味着在
与一个特定的类使用时,它的含义已发生了变化(用于String 的“+”和“+=”是Java 中能被覆盖的唯一运
算符,Java 不允许程序员覆盖其他任何运算符——注释④)。
373
…………………………………………………………Page 375……………………………………………………………
④:C++允许程序员随意覆盖运算符。由于这通常是一个复杂的过程(参见《Thinking in C++》,Prentice
Hall 于 1995 年出版),所以Java 的设计者认定它是一种“糟糕”的特性,决定不在 Java 中采用。但具有
讽剌意味的是,运算符的覆盖在Java 中要比在C++中容易得多。
针对String 对象使用时,“+”允许我们将不同的字串连接起来:
String s = 〃abc〃 + foo + 〃def〃 + Integer。toString(47);
可以想象出它“可能”是如何工作的:字串〃abc〃可以有一个方法append(),它新建了一个字串,其中包含
〃abc〃以及foo 的内容;这个新字串然后再创建另一个新字串,在其中添加〃def〃;以此类推。
这一设想是行得通的,但它要求创建大量字串对象。尽管最终的目的只是获得包含了所有内容的一个新字
串,但中间却要用到大量字串对象,而且要不断地进行垃圾收集。我怀疑 Java 的设计者是否先试过种方法
(这是软件开发的一个教训——除非自己试试代码,并让某些东西运行起来,否则不可能真正了解系统)。
我还怀疑他们是否早就发现这样做获得的性能是不能接受的。
解决的方法是象前面介绍的那样制作一个可变的同志类。对字串来说,这个同志类叫作StringBuffer,编译
器可以自动创建一个StringBuffer,以便计算特定的表达式,特别是面向String 对象应用覆盖过的运算符+
和+=时。下面这个例子可以解决这个问题:
//: ImmutableStrings。java
// Demonstrating StringBuffer
public class ImmutableStrings {
public static void main(String'' args) {
String foo = 〃foo〃;
String s = 〃abc〃 + foo +
〃def〃 + Integer。toString(47);
System。out。println(s);
// The 〃equivalent〃 using StringBuffer:
StringBuffer sb =
new StringBuffer(〃abc〃); // Creates String!
sb。append(foo);
sb。append(〃def〃); // Creates String!
sb。append(Integer。toString(47));
System。out。println(sb);
}
} ///:~
创建字串 s 时,编译器做的工作大致等价于后面使用 sb 的代码——创建一个StringBuffer,并用 append()
将新字符直接加入 StringBuffer 对象(而不是每次都产生新对象)。尽管这样做更有效,但不值得每次都创
建象〃abc〃和〃def〃这样的引号字串,编译器会把它们都转换成 String 对象。所以尽管StringBuffer 提供了
更高的效率,但会产生比我们希望的多得多的对象。
12。4。4 String 和 StringBuffer 类
这里总结一下同时适用于String 和StringBuffer 的方法,以便对它们相互间的沟通方式有一个印象。这些
表格并未把每个单独的方法都包括进去,而是包含了与本次讨论有重要关系的方法。那些已被覆盖的方法用
单独一行总结。
首先总结String 类的各种方法:
方法 自变量,覆盖 用途
构建器 已被覆盖:默认,String,StringBuffer,char 数组,byte 数组 创建String 对象
length() 无 String 中的字符数量
374
…………………………………………………………Page 376……………………………………………………………
charAt() int Index 位于String 内某个位置的char
getChars(),getBytes 开始复制的起点和终点,要向其中复制内容的数组,对目标数组的一个索引 将 char
或byte 复制到外部数组内部
toCharArray() 无 产生一个char'',其中包含了String 内部的字符
equals(),equalsIgnoreCase() 用于对比的一个 String 对两个字串的内容进行等价性检查
pareTo() 用于对比的一个String 结果为负、零或正,具体取决于String 和自变量的字典顺序。注意大
写和小写不是相等的!
regionMatches() 这个String 以及其他String 的位置偏移,以及要比较的区域长度。覆盖加入了“忽略大
小写”的特性 一个布尔结果,指出要对比的区域是否相同
startsWith() 可能以它开头的String。覆盖在自变量里加入了偏移 一个布尔结果,指出String 是否以那
个自变量开头
endsWith() 可能是这个 String 后缀的一个 String 一个布尔结果,指出自变量是不是一个后缀
indexOf();lastIndexOf() 已覆盖:char,char 和起始索引,String,String 和起始索引 若自变量未在这
个String 里找到,则返回…1;否则返回自变量开始处的位置索引。lastIndexOf()可从终点开始回溯搜索
substring() 已覆盖:起始索引,起始索引和结束索引 返回一个新的String 对象,其中包含了指定的字符
子集
concat() 想连结的String 返回一个新 String 对象,其中包含了原始 String 的字符,并在后面加上由自变
量提供的字符
relpace() 要查找的老字符,要用它替换的新字符 返回一个新 String 对象,其中已完成了替换工作。若没
有找到相符的搜索项,就沿用老字串
toLowerCase();toUpperCase() 无 返回一个新 String 对象,其中所有字符的大小写形式都进行了统一。若
不必修改,则沿用老字串
trim() 无 返回一个新的String 对象,头尾空白均已删除。若毋需改动,则沿用老字串
valueOf() 已覆盖:object,char'',char''和偏移以及计数,boolean,char,int,long,float,double
返回一个String,其中包含自变量的一个字符表现形式
Intern() 无 为每个独一无二的字符顺序都产生一个(而且只有一个)String 句柄
可以看到,一旦有必要改变原来的内容,每个 String 方法都小心地返回了一个新的 String 对象。另外要注
意的一个问题是,若内容不需要改变,则方法只返回指向原来那个String 的一个句柄。这样做可以节省存储
空间和系统开销。
下面列出有关StringBuffer (字串缓冲)类的方法:
方法 自变量,覆盖 用途
构建器 已覆盖:默认,要创建的缓冲区长度,要根据它创建的String 新建一个StringBuffer 对象
toString() 无 根据这个StringBuffer 创建一个 String
length() 无 StringBuffer 中的字符数量
capacity() 无 返回目前分配的空间大小
ensureCapacity() 用于表示希望容量的一个整数 使StringBuffer 容纳至少希望的空间大小
setLength() 用于指示缓冲区内字串新长度的一个整数 缩短或扩充前一个字符串。如果是扩充,则用 null
值填充空隙
charAt() 表示目标元素所在位置的一个整数 返回位于缓冲区指定位置处的 char
setCharAt() 代表目标元素位置的一个整数以及元素的一个新 char 值 修改指定位置处的值
getChars() 复制的起点和终点,要在其中复制的数组以及目标数组的一个索引 将 char 复制到一个外部数
组。和