八宝书库 > 文学其他电子书 > Java编程思想第4版[中文版](PDF格式) >

第59部分

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

小说: Java编程思想第4版[中文版](PDF格式) 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




础类或“接口”中已建立的方法才可在衍生类中被覆盖,如下面这张图所示:  

  



                                        

  

可将其描述成一种纯粹的“属于”关系,因为一个类的接口已规定了它到底“是什么”或者“属于什么”。 

通过继承,可保证所有衍生类都只拥有基础类的接口。如果按上述示意图操作,衍生出来的类除了基础类的 

接口之外,也不会再拥有其他什么。  

可将其想象成一种“纯替换”,因为衍生类对象可为基础类完美地替换掉。使用它们的时候,我们根本没必 

要知道与子类有关的任何额外信息。如下所示:  

  



                                             

  

也就是说,基础类可接收我们发给衍生类的任何消息,因为两者拥有完全一致的接口。我们要做的全部事情 

就是从衍生上溯造型,而且永远不需要回过头来检查对象的准确类型是什么。所有细节都已通过多形性获得 

了完美的控制。  

若按这种思路考虑问题,那么一个纯粹的“属于”关系似乎是唯一明智的设计方法,其他任何设计方法都会 

导致混乱不清的思路,而且在定义上存在很大的困难。但这种想法又属于另一个极端。经过细致的研究,我 

们发现扩展接口对于一些特定问题来说是特别有效的方案。可将其称为“类似于”关系,因为扩展后的衍生 

类“类似于”基础类——它们有相同的基础接口——但它增加了一些特性,要求用额外的方法加以实现。如 

下所示:  

  



                                                                       205 


…………………………………………………………Page 207……………………………………………………………

                               

  

尽管这是一种有用和明智的做法(由具体的环境决定),但它也有一个缺点:衍生类中对接口扩展的那一部 

分不可在基础类中使用。所以一旦上溯造型,就不可再调用新方法:  

  



                                            

  

若在此时不进行上溯造型,则不会出现此类问题。但在许多情况下,都需要重新核实对象的准确类型,使自 

己能访问那个类型的扩展方法。在后面的小节里,我们具体讲述了这是如何实现的。  



7。8。2  下溯造型与运行期类型标识  



由于我们在上溯造型(在继承结构中向上移动)期间丢失了具体的类型信息,所以为了获取具体的类型信 

息——亦即在分级结构中向下移动——我们必须使用  “下溯造型”技术。然而,我们知道一个上溯造型肯定 

是安全的;基础类不可能再拥有一个比衍生类更大的接口。因此,我们通过基础类接口发送的每一条消息都 

肯定能够接收到。但在进行下溯造型的时候,我们(举个例子来说)并不真的知道一个几何形状实际是一个 

圆,它完全可能是一个三角形、方形或者其他形状。  

  



                                                                             206 


…………………………………………………………Page 208……………………………………………………………

                                                  

  

为解决这个问题,必须有一种办法能够保证下溯造型正确进行。只有这样,我们才不会冒然造型成一种错误 

的类型,然后发出一条对象不可能收到的消息。这样做是非常不安全的。  

在某些语言中(如 C++),为了进行保证“类型安全”的下溯造型,必须采取特殊的操作。但在Java 中,所 

有造型都会自动得到检查和核实!所以即使我们只是进行一次普通的括弧造型,进入运行期以后,仍然会毫 

无留情地对这个造型进行检查,保证它的确是我们希望的那种类型。如果不是,就会得到一个 

ClassCastException (类造型违例)。在运行期间对类型进行检查的行为叫作“运行期类型标识” 

 (RTTI )。下面这个例子向大家演示了RTTI 的行为:  

  

//: RTTI。java  

// Downcasting & Run…Time Type  

// Identification (RTTI)  

import java。util。*;  

  

class Useful {  

  public void f() {}  

  public void g() {}  

}  

  

class MoreUseful extends Useful {  

  public void f() {}  

  public void g() {}  

  public void u() {}  

  public void v() {}  

  public void w() {}  

}  

  

public class RTTI {  

  public static void main(String'' args) {  

    Useful'' x = {  

      new Useful();  

      new MoreUseful()  

    };  

    x'0'。f();  

    x'1'。g();  

    // pile…time: method not found in Useful:  



                                                                                             207 


…………………………………………………………Page 209……………………………………………………………

    //! x'1'。u();  

    ((MoreUseful)x'1')。u(); // Downcast/RTTI  

    ((MoreUseful)x'0')。u(); // Exception thrown  

  }  

} ///:~  

  

和在示意图中一样,MoreUseful (更有用的)对Useful (有用的)的接口进行了扩展。但由于它是继承来 

的,所以也能上溯造型到一个Useful。我们可看到这会在对数组x (位于main()中)进行初始化的时候发 

生。由于数组中的两个对象都属于 Useful 类,所以可将 f()和g()方法同时发给它们两个。而且假如试图调 

用u() (它只存在于MoreUseful),就会收到一条编译期出错提示。  

若想访问一个MoreUseful 对象的扩展接口,可试着进行下溯造型。如果它是正确的类型,这一行动就会成 

功。否则,就会得到一个ClassCastException 。我们不必为这个违例编写任何特殊的代码,因为它指出的是 

一个可能在程序中任何地方发生的一个编程错误。  

RTTI 的意义远不仅仅反映在造型处理上。例如,在试图下溯造型之前,可通过一种方法了解自己处理的是什 

么类型。整个第 11章都在讲述 Java 运行期类型标识的方方面面。  



7。9 总结  



 “多形性”意味着“不同的形式”。在面向对象的程序设计中,我们有相同的外观(基础类的通用接口)以 

及使用那个外观的不同形式:动态绑定或组织的、不同版本的方法。  

通过这一章的学习,大家已知道假如不利用数据抽象以及继承技术,就不可能理解、甚至去创建多形性的一 

个例子。多形性是一种不可独立应用的特性(就象一个 switch 语句),只可与其他元素协同使用。我们应将 

其作为类总体关系的一部分来看待。人们经常混淆 Java 其他的、非面向对象的特性,比如方法过载等,这些 

特性有时也具有面向对象的某些特征。但不要被愚弄:如果以后没有绑定,就不成其为多形性。  

为使用多形性乃至面向对象的技术,特别是在自己的程序中,必须将自己的编程视野扩展到不仅包括单独一 

个类的成员和消息,也要包括类与类之间的一致性以及它们的关系。尽管这要求学习时付出更多的精力,但 

却是非常值得的,因为只有这样才可真正有效地加快自己的编程速度、更好地组织代码、更容易做出包容面 

广的程序以及更易对自己的代码进行维护与扩展。  



7。10 练习  



(1) 创建Rodent (啮齿动物):Mouse (老鼠);Gerbil (鼹鼠);Hamster (大颊鼠)等的一个继承分级结 

构。在基础类中,提供适用于所有 Rodent 的方法,并在衍生类中覆盖它们,从而根据不同类型的Rodent 采 

取不同的行动。创建一个Rodent 数组,在其中填充不同类型的 Rodent,然后调用自己的基础类方法,看看 

会有什么情况发生。  

(2) 修改练习 1,使Rodent 成为一个接口。  

(3) 改正WindError。java 中的问题。  

(4) 在GreenhouseControls。java 中,添加Event 内部类,使其能打开和关闭风扇。  



                                                                       208 


…………………………………………………………Page 210……………………………………………………………

                       第 8 章 对象的容纳  



  

 “如果一个程序只含有数量固定的对象,而且已知它们的存在时间,那么这个程序可以说是相当简单的。”  

  

通常,我们的程序需要根据程序运行时才知道的一些标准创建新对象。若非程序正式运行,否则我们根本不 

知道自己到底需要多少数量的对象,甚至不知道它们的准确类型。为了满足常规编程的需要,我们要求能在 

任何时候、任何地点创建任意数量的对象。所以不可依赖一个已命名的句柄来容纳自己的每一个对象,就象 

下面这样:  

MyObject myHandle;  

因为根本不知道自己实际需要多少这样的东西。  

为解决这个非常关键的问题,Java 提供了容纳对象(或者对象的句柄)的多种方式。其中内建的类型是数 

组,我们之前已讨论过它,本章准备加深大家对它的认识。此外,Java 的工具(实用程序)库提供了一些 

 “集合类”(亦称作“容器类”,但该术语已由AWT 使用,所以这里仍采用“集合”这一称呼)。利用这些 

集合类,我们可以容纳乃至操纵自己的对象。本章的剩余部分会就此进行详细讨论。  



8。1 数组  



对数组的大多数必要的介绍已在第 4 章的最后一节进行。通过那里的学习,大家已知道自己该如何定义及初 

始化一个数组。对象的容纳是本章的重点,而数组只是容纳对象的一种方式。但由于还有其他大量方法可容 

纳数组,所以是哪些地方使数组显得如此特别呢?  

有两方面的问题将数组与其他集合类型区分开来:效率和类型。对于Java 来说,为保存和访问一系列对象 

 (实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的 

访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在 

那个数组对象的“存在时间”内发生改变。可创建特定大小的一个数组,然后假如用光了存储空间,就再创 

建一个新数组,将所有句柄从旧数组移到新数组。这属于“矢量”(Vector)类的行为,本章稍后还会详细 

讨论它。然而,由于为这种大小的灵活性要付出较大的代价,所以我们认为矢量的效率并没有数组高。  

C++的矢量类知道自己容纳的是什么类型的对象,但同 Java 的数组相比,它却有一个明显的缺点:C++矢量类 

的operator''不能进行范围检查,所以很容易超出边界(然而,它可以查询 vector 有多大,而且at()方法 

确实能进行范围检查)。在Java 中,无论使用的是数组还是集合,都会进行范围检查——若超过边界,就会 

获得一个RuntimeException (运行期违例)错误。正如大家在第9 章会学到的那样,这类违例指出的是一个 

程序员错误,所以不需要在代码中检查它。在另一方面,由于 C++的vector 不进行范围检查,所以访问速度 

较快——在 Java 中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。  

本章还要学习另外几种常见的集合类:Vector (矢量)、Stack (堆栈)以及Hashtable (散列表)。这些类 

都涉及对对象的处理——好象它们没有特定的类型。换言之,它们将其当作 Object 类型处理(Object 类型 

是Java 中所有类的“根”类)。从某个角度看,这种处理方法是非常合理的:我们仅需构建一个集合,然后 

任何Java 对象都可以进入那个集合(除基本数据类型外——可用Java 的基本类型封装类将其作为常数置入 

集合,或者将其封装到自己的类内,作为可以变化的值使用)。这再一次反映了数组优于常规集合:创建一 

个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或 

者错误指定了准备提取的类型。当然,在编译期或者运行期,Java 会防止我们将不当的消息发给一个对象。 

所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目 

的也就达到了。此外,用户很少会对一次违例事件感到非常惊讶的。  

考虑到执行效率和类型检查,应尽可能地采用数组。然而,当我们试图解决一个更常规的问题时,数组的局 

限也可能显得非常明显。在研究过数组以后,本章剩余的部分将把重点放到Java 提供的集合类身上。  



8。1。1  数组和第一类对象  



无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存 

 “堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new 

表达式)。堆对象的一部分(实际是我们能访问的唯一字段或方法)是只读的 length (长度)成员,它告诉 

我们那个数组对象里最多能容纳多少元素。对于数组对象,“''”语法是我们能采用的唯一另类访问方法。  

下面这个例子展示了对数组进行初始化的不同方式,以及

返回目录 上一页 下一页 回到顶部 0 1

你可能喜欢的