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

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

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

…………………………………………………………Page 151……………………………………………………………

  

class Door {  

  public Window window = new Window();  

  public void open() {}  

  public void close() {}  

}  

  

public class Car {  

  public Engine engine = new Engine();  

  public Wheel'' wheel = new Wheel'4';  

  public Door left = new Door();  

       right = new Door(); // 2…door  

  Car() {  

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

      wheel'i' = new Wheel();  

  }  

  public static void main(String'' args) {  

    Car car = new Car();  

    car。left。window。rollup();  

    car。wheel'0'。inflate(72);  

  }  

} ///:~  

  

由于汽车的装配是故障分析时需要考虑的一项因素(并非只是基础设计简单的一部分),所以有助于客户程 

序员理解如何使用类,而且类创建者的编程复杂程度也会大幅度降低。  

如选择继承,就需要取得一个现成的类,并制作它的一个特殊版本。通常,这意味着我们准备使用一个常规 

用途的类,并根据特定的需求对其进行定制。只需稍加想象,就知道自己不能用一个车辆对象来合成一辆汽 

车——汽车并不“包含”车辆;相反,它“属于”车辆的一种类别。“属于”关系是用继承来表达的,而 

 “包含”关系是用合成来表达的。  



6。5 protected  



现在我们已理解了继承的概念,protected 这个关键字最后终于有了意义。在理想情况下,private 成员随时 

都是“私有”的,任何人不得访问。但在实际应用中,经常想把某些东西深深地藏起来,但同时允许访问衍 

生类的成员。protected 关键字可帮助我们做到这一点。它的意思是“它本身是私有的,但可由从这个类继 

承的任何东西或者同一个包内的其他任何东西访问”。也就是说,Java 中的protected 会成为进入“友好” 

状态。  

我们采取的最好的做法是保持成员的private 状态——无论如何都应保留对基 础的实施细节进行修改的权 

利。在这一前提下,可通过protected 方法允许类的继承者进行受到控制的访问:  

  

//: Orc。java  

// The protected keyword  

import java。util。*;  

  

class Villain {  

  private int i;  

  protected int read() { return i; }  

  protected void set(int ii) { i = ii; }  

  public Villain(int ii) { i = ii; }  

  public int value(int m) { return m*i; }  

}  

  

public class Orc extends Villain {  



                                                                                             150 


…………………………………………………………Page 152……………………………………………………………

  private int j;  

  public Orc(int jj) { super(jj); j = jj; }  

  public void change(int x) { set(x); }  

} ///:~  

  

可以看到,change()拥有对 set()的访问权限,因为它的属性是protected (受到保护的)。  



6。6 累积开发  



继承的一个好处是它支持“累积开发”,允许我们引入新的代码,同时不会为现有代码造成错误。这样可将 

新错误隔离到新代码里。通过从一个现成的、功能性的类继承,同时增添成员新的数据成员及方法(并重新 

定义现有方法),我们可保持现有代码原封不动(另外有人也许仍在使用它),不会为其引入自己的编程错 

误。一旦出现错误,就知道它肯定是由于自己的新代码造成的。这样一来,与修改现有代码的主体相比,改 

正错误所需的时间和精力就可以少很多。  

类的隔离效果非常好,这是许多程序员事先没有预料到的。甚至不需要方法的源代码来实现代码的再生。最 

多只需要导入一个包(这对于继承和合并都是成立的)。  

大家要记住这样一个重点:程序开发是一个不断递增或者累积的过程,就象人们学习知识一样。当然可根据 

要求进行尽可能多的分析,但在一个项目的设计之初,谁都不可能提前获知所有的答案。如果能将自己的项 

目看作一个有机的、能不断进步的生物,从而不断地发展和改进它,就有望获得更大的成功以及更直接的反 

馈。  

尽管继承是一种非常有用的技术,但在某些情况下,特别是在项目稳定下来以后,仍然需要从新的角度考察 

自己的类结构,将其收缩成一个更灵活的结构。请记住,继承是对一种特殊关系的表达,意味着“这个新类 

属于那个旧类的一种类型”。我们的程序不应纠缠于一些细树末节,而应着眼于创建和操作各种类型的对 

象,用它们表达出来自“问题空间”的一个模型。  



6。7 上溯造型  



继承最值得注意的地方就是它没有为新类提供方法。继承是对新类和基础类之间的关系的一种表达。可这样 

总结该关系:“新类属于现有类的一种类型”。  

这种表达并不仅仅是对继承的一种形象化解释,继承是直接由语言提供支持的。作为一个例子,大家可考虑 

一个名为 Instrument 的基础类,它用于表示乐器;另一个衍生类叫作Wind 。由于继承意味着基础类的所有 

方法亦可在衍生出来的类中使用,所以我们发给基础类的任何消息亦可发给衍生类。若 Instrument 类有一个 

play()方法,则 Wind 设备也会有这个方法。这意味着我们能肯定地认为一个Wind 对象也是 Instrument 的一 

种类型。下面这个例子揭示出编译器如何提供对这一概念的支持:  

  

//: Wind。java  

// Inheritance & upcasting  

import java。util。*;  

  

class Instrument {  

  public void play() {}  

  static void tune(Instrument i) {  

    // 。。。  

    i。play();  

  }  

}  

  

// Wind objects are instruments  

// because they have the same interface:  

class Wind extends Instrument {  

  public static void main(String'' args) {  

    Wind flute = new Wind();  

    Instrument。tune(flute); // Upcasting  



                                                                                 151 


…………………………………………………………Page 153……………………………………………………………

  }  

} ///:~  

  

这个例子中最有趣的无疑是tune()方法,它能接受一个 Instrument句柄。但在 Wind。main()中,tune()方法 

是通过为其赋予一个Wind 句柄来调用的。由于Java 对类型检查特别严格,所以大家可能会感到很奇怪,为 

什么接收一种类型的方法也能接收另一种类型呢?但是,我们一定要认识到一个 Wind 对象也是一个 

Instrument对象。而且对于不在 Wind 中的一个Instrument (乐器),没有方法可以由tune()调用。在 

tune()中,代码适用于 Instrument 以及从 Instrument 衍生出来的任何东西。在这里,我们将从一个Wind 句 

柄转换成一个 Instrument 句柄的行为叫作“上溯造型”。  



6。7。1  何谓“上溯造型”?  



之所以叫作这个名字,除了有一定的历史原因外,也是由于在传统意义上,类继承图的画法是根位于最顶 

部,再逐渐向下扩展(当然,可根据自己的习惯用任何方法描绘这种图)。因素,Wind。java 的继承图就象 

下面这个样子:  



             

由于造型的方向是从衍生类到基础类,箭头朝上,所以通常把它叫作“上溯造型”,即Upcasting。上溯造 

型肯定是安全的,因为我们是从一个更特殊的类型到一个更常规的类型。换言之,衍生类是基础类的一个超 

集。它可以包含比基础类更多的方法,但它至少包含了基础类的方法。进行上溯造型的时候,类接口可能出 

现的唯一一个问题是它可能丢失方法,而不是赢得这些方法。这便是在没有任何明确的造型或者其他特殊标 

注的情况下,编译器为什么允许上溯造型的原因所在。  

也可以执行下溯造型,但这时会面临第 11章要详细讲述的一种困境。  

  

1。 再论合成与继承  

在面向对象的程序设计中,创建和使用代码最可能采取的一种做法是:将数据和方法统一封装到一个类里, 

并且使用那个类的对象。有些时候,需通过“合成”技术用现成的类来构造新类。而继承是最少见的一种做 

法。因此,尽管继承在学习OOP 的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相 

反,使用它时要特别慎重。只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。为判断自己到 

底应该选用合成还是继承,一个最简单的办法就是考虑是否需要从新类上溯造型回基础类。若必须上溯,就 

需要继承。但如果不需要上溯造型,就应提醒自己防止继承的滥用。在下一章里(多形性),会向大家介绍 

必须进行上溯造型的一种场合。但只要记住经常问自己“我真的需要上溯造型吗”,对于合成还是继承的选 

择就不应该是个太大的问题。  



6。8 final 关键字  



由于语境(应用环境)不同,final 关键字的含义可能会稍微产生一些差异。但它最一般的意思就是声明 

 “这个东西不能改变”。之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。由于这两个原因颇 

有些区别,所以也许会造成final 关键字的误用。  

在接下去的小节里,我们将讨论final 关键字的三种应用场合:数据、方法以及类。  



6。8。1 final 数据  



许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:  

(1) 编译期常数,它永远不会改变  

(2) 在运行期初始化的一个值,我们不希望它发生变化  

对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期 

间提前执行,从而节省运行时的一些开销。在 Java 中,这些形式的常数必须属于基本数据类型 

 (Primitives),而且要用final 关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个 



                                                                   152 


…………………………………………………………Page 154……………………………………………………………

值。  

无论 static还是 final字段,都只能存储一个数据,而且不得改变。  

若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类 

型,final 会将值变成一个常数;但对于对象句柄,final 会将句柄变成一个常数。进行声明时,必须将句柄 

初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。Java 

对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有 

 “常数”效果)。这一限制也适用于数组,它也属于对象。  

下面是演示 final字段用法的一个例子:  

  

//: FinalData。java  

// The effect of final on fields  

  

class Value {  

  int i = 1;  

}  

  

public class FinalData {  

  // Can be pile…time constants  

  final int i1 = 9;  

  static final int I2 = 99;  

  // Typical public constant:  

  public static final int I3 = 39;  

  // Cannot be pile…time constants:  

  final int i4 = (int)(Math。random()*20);  

  static final int i5 = (int)(Math。random()*20);  

    

  Value v1 = new Value();  

  final Value v2 = new Value();  

  static final Value v3 = new Value();  

  //! final Value v4; // Pre…Java 1。1 Error:   

                      // no initializer  

  // Arrays:  

  final int'' a = { 1; 2; 3; 4; 5; 6 };  

  

  public void print(String id) {  

    System。out。println(  

      id + 〃: 〃 + 〃i4 = 〃 + i4 +   

      〃; i5 = 〃 + i5);  

  }  

  public static void main(String'' args) {  

    FinalData fd1 = new FinalData();  

    //! fd1。i1++; // Error: can't change value  

    fd1。v2。i++; // Object isn't constant!  

    fd1。v1 = new Value(); // OK …not final  

    for(int i = 0; i 《 fd1。a。length; i++)  

      fd1。a'i'++; // Object isn't constant!  

    //! fd1。v2 = new Value(); // Error: Can't   

    //! fd1。v3 = new Value(); // change handle  

    //! fd1。a = new int'3';  

  

    fd1。print(〃fd1〃);  

    System。out。println(〃Creating new FinalData〃);  



                                                                                             153 


…………………………………………………………Page 155……………………………………………………………

    FinalData fd2 = new FinalData();  

    fd1。print(〃fd1〃);  

    fd2。print(〃fd2〃);  

  }  

} ///:~  

  

由于i1和 I2都是具有 final 属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数 

使用外,在任何导入方式中也不会出现任何不同。I3是我们体验此类常数定义时更典型的一种方式:public 

表示它们可在包外使用;Static 强调它们只有一个;而 final 表明它是一个常数。注意对于含有固定初始化 

值(即编译期常数)的 fianl static基本数据类型,它们的名字根据规则要全部采用大写。也要注意 i5 在 

编译期间是未知的,所以它没有大写。  

不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4 和 i5 向大家证明了这一点。它们在 

运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final 值设为 static 和非 static 之间的 

差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相 

同的。这种差异可从输出结果中看出:  

  

fd1: i4 = 15; i5 = 9  

Creating new FinalData  

fd1: i4 = 15; i5 = 9  

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