友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第90部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


  

//: Immutable1。java  

// Objects that cannot be modified  

// are immune to aliasing。  

  

public class Immutable1 {  

  private int data;  

  public Immutable1(int initVal) {  

    data = initVal;  

  }  

  public int read() { return data; }  



                                                                                             370 


…………………………………………………………Page 372……………………………………………………………

  public boolean nonzero() { return data != 0; }  

  public Immutable1 quadruple() {  

    return new Immutable1(data * 4);  

  }  

  static void f(Immutable1 i1) {  

    Immutable1 quad = i1。quadruple();  

    System。out。println(〃i1 = 〃 + i1。read());  

    System。out。println(〃quad = 〃 + quad。read());  

  }  

  public static void main(String'' args)  {  

    Immutable1 x = new Immutable1(47);  

    System。out。println(〃x = 〃 + x。read());  

    f(x);  

    System。out。println(〃x = 〃 + x。read());  

  }  

} ///:~  

  

所有数据都设为private,可以看到没有任何public 方法对数据作出修改。事实上,确实需要修改一个对象 

的方法是quadruple(),但它的作用是新建一个Immutable1 对象,初始对象则是原封未动的。  

方法 f()需要取得一个 Immutable1对象,并对其采取不同的操作,而 main()的输出显示出没有对x 作任何修 

改。因此,x 对象可别名处理许多次,不会造成任何伤害,因为根据 Immutable1类的设计,它能保证对象不 

被改动。  



12。4。2  “一成不变”的弊端  



从表面看,不变类的建立似乎是一个好方案。但是,一旦真的需要那种新类型的一个修改的对象,就必须辛 

苦地进行新对象的创建工作,同时还有可能涉及更频繁的垃圾收集。对有些类来说,这个问题并不是很大。 

但对其他类来说(比如 String 类),这一方案的代价显得太高了。  

为解决这个问题,我们可以创建一个“同志”类,并使其能够修改。以后只要涉及大量的修改工作,就可换 

为使用能修改的同志类。完事以后,再切换回不可变的类。  

因此,上例可改成下面这个样子:  

  

//: Immutable2。java  

// A panion class for making changes  

// to immutable objects。  

  

class Mutable {  

  private int data;  

  public Mutable(int initVal) {  

    data = initVal;  

  }  

  public Mutable add(int x) {   

    data += x;  

    return this;  

  }  

  public Mutable multiply(int x) {  

    data *= x;  

    return this;  

  }  

  public Immutable2 makeImmutable2() {  

    return new Immutable2(data);  

  }  

}  



                                                                                             371 


…………………………………………………………Page 373……………………………………………………………

  

public class Immutable2 {  

  private int data;  

  public Immutable2(int initVal) {  

    data = initVal;  

  }  

  public int read() { return data; }  

  public boolean nonzero() { return data != 0; }  

  public Immutable2 add(int x) {   

    return new Immutable2(data + x);  

  }  

  public Immutable2 multiply(int x) {  

    return new Immutable2(data * x);  

  }  

  public Mutable makeMutable() {  

    return new Mutable(data);  

  }  

  public static Immutable2 modify1(Immutable2 y){  

    Immutable2 val = y。add(12);  

    val = val。multiply(3);  

    val = val。add(11);  

    val = val。multiply(2);  

    return val;  

  }  

  // This produces the same result:  

  public static Immutable2 modify2(Immutable2 y){  

    Mutable m = y。makeMutable();  

    m。add(12)。multiply(3)。add(11)。multiply(2);  

    return m。makeImmutable2();  

  }  

  public static void main(String'' args) {  

    Immutable2 i2 = new Immutable2(47);  

    Immutable2 r1 = modify1(i2);  

    Immutable2 r2 = modify2(i2);  

    System。out。println(〃i2 = 〃 + i2。read());  

    System。out。println(〃r1 = 〃 + r1。read());  

    System。out。println(〃r2 = 〃 + r2。read());  

  }  

} ///:~  

  

和往常一样,Immutable2 包含的方法保留了对象不可变的特征,只要涉及修改,就创建新的对象。完成这些 

操作的是add()和multiply()方法。同志类叫作 Mutable,它也含有 add()和 multiply()方法。但这些方法 

能够修改Mutable 对象,而不是新建一个。除此以外,Mutable 的一个方法可用它的数据产生一个 

Immutable2对象,反之亦然。  

两个静态方法modify1()和 modify2()揭示出获得同样结果的两种不同方法。在 modify1()中,所有工作都是 

在 Immutable2 类中完成的,我们可看到在进程中创建了四个新的 Immutable2 对象(而且每次重新分配了 

val,前一个对象就成为垃圾)。  

在方法modify2()中,可看到它的第一个行动是获取 Immutable2 y,然后从中生成一个Mutable (类似于前 

面对 clone()的调用,但这一次创建了一个不同类型的对象)。随后,用Mutable 对象进行大量修改操作, 

同时用不着新建许多对象。最后,它切换回Immutable2。在这里,我们只创建了两个新对象(Mutable 和 

Immutable2 的结果),而不是四个。  

这一方法特别适合在下述场合应用:  



                                                                                          372 


…………………………………………………………Page 374……………………………………………………………

(1) 需要不可变的对象,而且  

(2) 经常需要进行大量修改,或者  

(3) 创建新的不变对象代价太高  



12。4。3 不变字串  



请观察下述代码:  

  

//: 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() 
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!