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

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

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


个违例,便不能通过继承来进行修改,并可有效地禁止克隆(不能从一个拥有任意继承级数的类中明确调用 

Object。clone();只能调用 super。clone(),它只可访问直接基础类)。因此,只要制作一些涉及安全问题 

的对象,就最好把那些类设为final。  

在类CheckCloneable 中,我们看到的第一个类是tryToClone(),它能接纳任何Ordinary 对象,并用 

instanceof检查它是否能够克隆。若答案是肯定的,就将对象造型成为一个 IsCloneable,调用clone(), 

并将结果造型回Ordinary,最后捕获有可能产生的任何违例。请注意用运行期类型鉴定(见第 11章)打印 

出类名,使自己看到发生的一切情况。  

在main()中,我们创建了不同类型的Ordinary 对象,并在数组定义中上溯造型成为 Ordinary 。在这之后的 

头两行代码创建了一个纯粹的 Ordinary 对象,并试图对其克隆。然而,这些代码不会得到编译,因为 

clone()是 Object 中的一个protected (受到保护的)方法。代码剩余的部分将遍历数组,并试着克隆每个 

对象,分别报告它们的成功或失败。输出如下:  

  

Attempting IsCloneable  

Cloned IsCloneable  



                                                                                   365 


…………………………………………………………Page 367……………………………………………………………

Attempting NoMore  

Could not clone NoMore  

Attempting TryMore  

Could not clone TryMore  

Attempting BackOn  

Cloned BackOn  

Attempting ReallyNoMore  

Could not clone ReallyNoMore  

  

总之,如果希望一个类能够克隆,那么:  

(1) 实现Cloneable 接口  

(2) 覆盖 clone()  

(3) 在自己的clone()中调用super。clone()  

(4) 在自己的clone()中捕获违例  

这一系列步骤能达到最理想的效果。  



12。3。1 副本构建器  



克隆看起来要求进行非常复杂的设置,似乎还该有另一种替代方案。一个办法是制作特殊的构建器,令其负 

责复制一个对象。在C++中,这叫作“副本构建器”。刚开始的时候,这好象是一种非常显然的解决方案 

 (如果你是 C++程序员,这个方法就更显亲切)。下面是一个实际的例子:  

  

//: CopyConstructor。java  

// A constructor for copying an object  

// of the same type; as an attempt to create  

// a local copy。  

  

class FruitQualities {  

  private int weight;  

  private int color;  

  private int firmness;  

  private int ripeness;  

  private int smell;  

  // etc。  

  FruitQualities() { // Default constructor  

    // do something meaningful。。。  

  }  

  // Other constructors:  

  // 。。。  

  // Copy constructor:  

  FruitQualities(FruitQualities f) {  

    weight = f。weight;  

    color = f。color;  

    firmness = f。firmness;  

    ripeness = f。ripeness;  

    smell = f。smell;  

    // etc。  

  }  

}  

  

class Seed {  

  // Members。。。  

  Seed() { /* Default constructor */ }  



                                                                                             366 


…………………………………………………………Page 368……………………………………………………………

  Seed(Seed s) { /* Copy constructor */ }  

}  

  

class Fruit {  

  private FruitQualities fq;  

  private int seeds;  

  private Seed'' s;  

  Fruit(FruitQualities q; int seedCount) {   

    fq = q;  

    seeds = seedCount;  

    s = new Seed'seeds';  

    for(int i = 0; i 《 seeds; i++)  

      s'i' = new Seed();  

  }  

  // Other constructors:  

  // 。。。  

  // Copy constructor:  

  Fruit(Fruit f) {  

    fq = new FruitQualities(f。fq);  

    seeds = f。seeds;  

    // Call all Seed copy…constructors:  

    for(int i = 0; i 《 seeds; i++)  

      s'i' = new Seed(f。s'i');  

    // Other copy…construction activities。。。  

  }  

  // To allow derived constructors (or other   

  // methods) to put in different qualities:  

  protected void addQualities(FruitQualities q) {  

    fq = q;  

  }  

  protected FruitQualities getQualities() {  

    return fq;  

  }  

}  

  

class Tomato extends Fruit {  

  Tomato() {  

    super(new FruitQualities(); 100);  

  }  

  Tomato(Tomato t) { // Copy…constructor  

    super(t); // Upcast for base copy…constructor  

    // Other copy…construction activities。。。  

  }  

}  

  

class ZebraQualities extends FruitQualities {  

  private int stripedness;  

  ZebraQualities() { // Default constructor  

    // do something meaningful。。。  

  }  

  ZebraQualities(ZebraQualities z) {  

    super(z);  



                                                                                             367 


…………………………………………………………Page 369……………………………………………………………

    stripedness = z。stripedness;  

  }  

}  

  

class GreenZebra extends Tomato {  

  GreenZebra() {  

    addQualities(new ZebraQualities());  

  }  

  GreenZebra(GreenZebra g) {  

    super(g); // Calls Tomato(Tomato)  

    // Restore the right qualities:  

    addQualities(new ZebraQualities());  

  }  

  void evaluate() {  

    ZebraQualities zq =   

      (ZebraQualities)getQualities();  

    // Do something with the qualities  

    // 。。。  

  }  

}  

  

public class CopyConstructor {  

  public static void ripen(Tomato t) {  

    // Use the 〃copy constructor〃:  

    t = new Tomato(t);   

    System。out。println(〃In ripen; t is a 〃 +  

      t。getClass()。getName());  

  }  

  public static void slice(Fruit f) {  

    f = new Fruit(f); // Hmmm。。。 will this work?  

    System。out。println(〃In slice; f is a 〃 +  

      f。getClass()。getName());  

  }  

  public static void main(String'' args) {  

    Tomato tomato = new Tomato();  

    ripen(tomato); // OK  

    slice(tomato); // OOPS!  

    GreenZebra g = new GreenZebra();  

    ripen(g); // OOPS!  

    slice(g); // OOPS!  

    g。evaluate();  

  }  

} ///:~  

  

这个例子第一眼看上去显得有点奇怪。不同水果的质量肯定有所区别,但为什么只是把代表那些质量的数据 

成员直接置入Fruit (水果)类?有两方面可能的原因。第一个是我们可能想简便地插入或修改质量。注意 

Fruit 有一个protected (受到保护的)addQualities()方法,它允许衍生类来进行这些插入或修改操作(大 

家或许会认为最合乎逻辑的做法是在Fruit 中使用一个protected 构建器,用它获取FruitQualities 参数, 

但构建器不能继承,所以不可在第二级或级数更深的类中使用它)。通过将水果的质量置入一个独立的类, 

可以得到更大的灵活性,其中包括可以在特定 Fruit 对象的存在期间中途更改质量。  

之所以将FruitQualities 设为一个独立的对象,另一个原因是考虑到我们有时希望添加新的质量,或者通过 

继承与多形性改变行为。注意对GreenZebra 来说(这实际是西红柿的一类——我已栽种成功,它们简直令人 



                                                                                             368 


…………………………………………………………Page 370……………………………………………………………

难以置信),构建器会调用addQualities(),并为其传递一个ZebraQualities 对象。该对象是从 

FruitQualities 衍生出来的,所以能与基础类中的 FruitQualities 句柄联系在一起。当然,一旦 

GreenZebra 使用 FruitQualities,就必须将其下溯造型成为正确的类型(就象evaluate()中展示的那 

样),但它肯定知道类型是ZebraQualities。  

大家也看到有一个 Seed (种子)类,Fruit (大家都知道,水果含有自己的种子)包含了一个Seed 数组。  

最后,注意每个类都有一个副本构建器,而且每个副本构建器都必须关心为基础类和成员对象调用副本构建 

器的问题,从而获得“深层复制”的效果。对副本构建器的测试是在 CopyConstructor 类内进行的。方法 

ripen()需要获取一个Tomato 参数,并对其执行副本构建工作,以便复制对象:  

t = new Tomato(t);  

而 slice()需要获取一个更常规的 Fruit 对象,而且对它进行复制:  

f = new Fruit(f);  

它们都在main()中伴随不同种类的Fruit 进行测试。下面是输出结果:  

  

In ripen; t is a Tomato  

In slice; f is a Fruit  

In ripen; t is a Tomato  

In slice; f is a Fruit  

  

从中可以看出一个问题。在slice()内部对Tomato 进行了副本构建工作以后,结果便不再是一个 Tomato 对 

象,而只是一个Fruit。它已丢失了作为一个Tomato (西红柿)的所有特征。此外,如果采用一个 

GreenZebra,ripen()和 slice()会把它分别转换成一个 Tomato 和一个 Fruit。所以非常不幸,假如想制作对 

象的一个本地副本,Java 中的副本构建器便不是特别适合我们。  

  

1。 为什么在C++的作用比在 Java 中大?  

副本构建器是C++的一个基本构成部分,因为它能自动产生对象的一个本地副本。但前面的例子确实证明了 

它不适合在 Java 中使用,为什么呢?在 Java 中,我们操控的一切东西都是句柄,而在C++中,却可以使用 

类似于句柄的东西,也能直接传递对象。这时便要用到C++的副本构建器:只要想获得一个对象,并按值传 

递它,就可以复制对象。所以它在 C++里能很好地工作,但应注意这套机制在Java 里是很不通的,所以不要 

用它。  



12。4 只读类  



尽管在一些特定的场合,由clone()产生的本地副本能够获得我们希望的结果,但程序员(方法的作者)不 

得不亲自禁止别名处理的副作用。假如想制作一个库,令其具有常规用途,但却不能担保它肯定能在正确的 

类中得以克隆,这时又该怎么办呢?更有可能的一种情况是,假如我们想让别名发挥积极的作用——禁止不 

必要的对象复制——但却不希望看到由此造成的副作用,那么又该如何处理呢?  

一个办法是创建“不变对象”,令其从属于只读类。可定义一个特殊的类,使其中没有任何方法能造成对象 

内部状态的改变。在这样的一个类中,别名处理是没有问题的。因为我们只能读取内部状态,所以当多处代 

码都读取相同的对象时,不会出现任何副作用。  

作为“不变对象”一个简单例子,Java 的标准库包含了“封装器”(wrapper )类,可用于所有基本数据类 

型。大家可能已发现了这一点,如果想在一个象Vector (只采用Object 句柄)这样的集合里保存一个 int 

数值,可以将这个 int 封装到标准库的 Integer类内部。如下所示:  

  

//: ImmutableInteger。java  

// The Integer class cannot be changed  

import java。util。*;  

  

public class ImmutableInteger {  

  public static void main(String'' args) {  

    Vector v = new Vector();  

    for(int i = 0; i 《 10; i++)   

      v。addElement(new Integer(i));  

    // But how do you change the int  



                                                                                 369 


…………………………………………………………Page 371……………………………………………………………

    // inside the Integer?  

  }  

} ///:~  

  

Integer类(以及基本的“封装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方 

法。  

若确实需要一个容纳了基本数据类型的对象,并想对基本数据类型进行修改,就必须亲自创建它们。幸运的 

是,操作非常简单:  

  

//: MutableInteger。java  

// A changeable wrapper class  

import java。util。*;  

  

class IntValue {   

  int n;  

  IntValue(int x) { n = x; }  

  public String toString() {   

    return Integer。toString(n);  

  }  

}  

  

public class MutableInteger {  

  public static void main(String'' args) {  

    Vector v = new Vector();  

    for(int i = 0; i 《 10; i++)   

      v。addElement(new IntValue(i));  

    System。out。println(v);  

    for(int i = 0; i 《 v。size(); i++)  

      ((IntValue)v。elementAt(i))。n++;  

    System。out。println(v);  

  }  

} ///:~  

  

注意n 在这里简化了我们的编码。  

若默认的初始化为零已经足够(便不需要构建器),而且不用考虑把它打印出来(便不需要 toString ),那 

么 IntValue 甚至还能更加简单。如下所示:  

class IntValue { int n; }  

将元素取出来,再对其进行造型,这多少显得有些笨拙,但那是Vector 的问题,不是IntValue 的错。  



12。4。1 创建只读类  



完全可以创建自己的只读类,下面是个简单的例子:  

  

//: Immutable1。java  

// Objects that cannot be modified  

// are immune to aliasing。  

  

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