1.5 组织类和类行为
21天学通Java(第6版)
Java面向对象编程还涉及另外三个概念:继承、接口和包,这些都是用于组织类和类行为的机制。
1.5.1 继承
继承是面向对象编程中最重要的概念之一,直接影响您如何设计和编写Java类。
继承是一种机制,让一个类能够继承另一个类的所有行为和属性。
通过继承,一个类可自动拥有现有类的所有功能,因此只需定义与现有类不同的地方。
通过继承,所有的类(无论是您创建的类,还是Java类库中的类)都以严格的层次结构来组织。
继承其他类的类叫子类,被继承的类叫超类。
一个类只能有一个超类,但可以有任意数目的子类。子类继承了其超类的所有属性和行为。
实际上,这意味着如果超类具备您的类所需的行为和属性,则无需重新定义或复制代码,便可获得同样的行为和属性。子类将自动从其超类那儿获得这些东西,而超类又从其超类获得相应的东西,依此类推。这样便形成了层次结构。子类将拥有层次结构中位于它上面所有类的特性,同时也有自己的特性。
这与您从父母那里继承各种东西(如身高、头发颜色、喜欢花生黄油和香蕉三明治)相同。它们也从其父母那里继承了一些特征,它们的父母又是从它们父母的父母那里继承,这样一直追溯到伊甸园、宇宙大爆炸或大爆炸之前。
图1.3显示了类的层次排列方式。
Java类层次结构的顶端是类Object。
所有的类都是从这个超类继承而来的。Object是层次结构中最通用的类,定义了Java类库中的所有类的行为。
在层次结构中越往下,类的用途越具体。在层次结构的顶部定义的是抽象概念,越往下,这些概念越具体。
使用Java创建新类时,常常希望它具备某个现有类的所有功能,并做一些修改。例如,您可能希望有一个新版本的CommandButton,能够在单击时发出声音。
要不经过任何重建工作而得到CommandButton的所有功能,可以将您的类定义为CommandButton的子类。
这样,您的类将自动继承CommandButton定义的行为和属性以及CommandButton的超类定义的行为和属性。您所需要关心的只是新类不同于CommandButton的内容。子类化(subclassing)机制用于定义新类及其与超类之间的差别。
子类化指的是通过继承已有的类来创建一个新类。子类只需指出其属性和行为不同于超类的地方。
如果您的类定义了全新的行为,且不是其他类的子类,则可以直接继承Object类。
如果您创建类时没有指定超类,Java将认为它直接继承Object。前面创建的VolcanoRobot类没有指定超类,因此是Object的子类。
1.5.2 创建类层次结构
如果您创建了大量的类,则应该让您的类从现有类层次结构继承,并构建自身的层次结构。这有如下优点:
可将多个类共有的功能放在一个超类中,这样就可以在更底层的类中重复使用这些功能;
对超类的修改将自动反映到其所有的子类、子类的子类等中,而无需修改或重新编译更底层的类,它们将通过继承获得新的信息。
例如,假设创建了一个Java类来实现火山勘测机器人的所有特征(这并不需要太多的想象力)。
该VolcanoRobot类已经完成,它工作正常,一切都很好。现在您在NASA的老板要求您创建一个名为MarsRobot的Java类。
这两种机器人有相似的特征:都是在恶劣环境下执行研究工作的机器人,且都跟踪其当前的温度和速度。
您首先想到的可能是,打开源代码文件VolcanoRobot.java,将其大部分代码复制到新的源代码文件MarsRobot.java,再根据新机器人的用途做必要的修改。
更好的办法是找出MarsRobot和VolcanoRobot的共同功能,并将它们放到一个更通用的类层次结构中。对于只有类VolcanoRobot和MarsRobot的情况,这也许是项繁重的工作,但如果您还想加入MoonRobot、UnderseaRobot和DesertRobot,情况将如何呢?将共同的行为放在一个或多个可重用的超类中将极大地减少所需完成的工作量。
要设计一个满足该目标的类层次,应从Object开始,它是所有Java类的祖宗。
这些机器人的老祖宗可能名为Robot。一般而言,机器人可被视为一种自控的探测设备。在Robot类中,您只定义使其成为自控的、用于探测的设备的行为。
在Robot下面有两个类:WalkingRobot和DrivingRobot。这两个类之间明显的区别在于,一个靠腿移动,另一个靠轮子移动。步行机器人的行为可能包括弯腰检东西、蹲下、跑动等。驱动式机器人的行为与此不同。图1.4显示了您目前已有的类层次结构。
现在,这个层次可以更具体。从WalkingRobot类可以派生出多个类:ScienceRobot、GuardRobot、SearchRobot等。另外,您可以抽取出更多的功能,创建两个中间类TwoLegged和FourLegged,其中每个类都有不同的行为(见图1.5)。
最后,整个层次结构便完成了,并为VolcanoRobot找到了合适的位置。它可以是ScienceRobot的子类,ScienceRobot是WalkingRobot的子类,WalkingRobot是Robot的子类,而Robot又是Object的子类。
诸如status、temperature和speed等属性应放在什么位置呢?应放在最合适的地方。因为所有机器人都需要跟踪其所处环境的温度,因此在Robot中将temperature定义为一个实例变量是合理的。这样所有的子类都将有这个实例变量。请记住,只需在层次结构定义行为或属性一次,它将自动被每个子类继承。
注意要设计出高效的类层次结构,需要做大量的规划和修订。当您试图将新的属性和行为加入到层次结构中时,很可能发现需要将一些类移到另一个位置,以便减少重复的特征和冗余的代码。
1.5.3 使用继承
在Java中,继承比现实生活中的继承要简单得多。Java中继承时,不需要遗嘱,也不需要法庭。
当您创建新对象时,Java将记录该对象及其超类的每个变量。这样,所有的类组合成当前对象的模板,每个对象都将包含合适的信息。
方法的工作原理与此相似,新对象可以访问其所属类及其超类的所有方法,这是在运行期间当方法被使用时动态确定的。如果您调用了特定对象的某个方法,Java虚拟机将首先检查该对象所属的类是否有该方法。如果没有,则在其超类中查找,依此类推,直到找到该方法的定义为止,如图1.6所示。
如果子类中定义了名称和其他方面都与超类相同的方法,情况将复杂起来。在这种情况下,首先被找到的方法是被使用的方法(从层次结构的底部开始向上查找)。
因此,可以在子类中创建一个方法来防止调用超类中定义的方法。为此,该方法的名称、返回值和参数必须与超类方法相同。这被称为覆盖,如图1.7所示。
注意 Java的继承形式称为单继承(single inheritance),因为每个Java类都只能有一个超类(虽然任何超类都可以有多个子类)。
在其他面向对象编程语言(如C++)中,类可以有多个超类,并继承所有超类的变量和方法,这叫多重继承(multiple inheritance)。Java只允许单继承,简化了继承机制。
1.5.4 接口
单继承简化了类之间的关系,并使这些类实现的功能更容易理解和设计。然而,它也有局限性,尤其是当您有一些相似的行为,它们需要在类层次结构的不同分支间进行复制时。Java通过使用接口来解决这些共享行为的问题。
接口是一组方法,它指出类除了从超类继承的行为外,还有其他行为。接口中的方法并没有定义行为,这项任务将由实现该接口的类去完成。
例如,Comparable接口包含一个这样的方法,即对属于同一个类的两个对象进行比较,以判断在排序链表中,哪个应在前面。任何实现了该接口的类都告诉其他对象,它能够确定其对象的排列顺序。如果没有该接口,类将不会有这种行为。
有关接口的内容将在第6章介绍。
1.5.6 包
在Java中,包用于将相关的类和接口编组,使得更容易在其他类中引用它们;包还避免了类之间潜在的命名冲突。
引用Java类时,可使用简短的名称,如Object,也可使用完整的名称,如java.lang.Object。
默认情况下,您的Java类只需通过简短的名称就可引用java.lang包中的类。java.lang包提供了基本的语言功能,如字符串处理和数学运算。要使用其他包中的类,必须使用完整的包名或使用import语句将包导入到源代码文件中。
例如,由于类Color位于包java.awt中,所以在程序中通常要使用java.awt.Color来引用它。
如果使用import语句导入了java.awt包,便可使用Color来引用这个类。