您的位置: 网站首页 > 程序开发 > Java程序设计 > 第4章 继承与多态 > 【4.1 继 承】

4.1 继 承

 

本章讨论面向对象程序设计的另外两个重要特点:继承与多态。

继承是面向对象程序设计方法中实现软件重用的一种重要手段。通过继承可以更有效地组织程序结构,明确类之间的关系,并充分利用已有的类来创建新类,以完成更复杂的程序设计与开发。

多态性是面向对象设计语言的基本特征。仅仅是将数据和函数捆绑在一起进行类的封装,并使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。面向对象程序设计的真正力量在于将派生类对象当基类对象一样处理的能力。支持这种能力的机制就是多态和动态绑定。多态性是面向对象的精髓,也是难点。

本章主要内容

&        继承概念

&        继承的实现

&        方法和成员变量的权限控制

&        面向对象的多态特性

&        Object类:所有类的超类

4.1     

继承是Java不可分割的一部分。实际上当创建类的时候就是在继承,要么是显式地继承别的类,要么是隐含地继承了标准Java根类,即Object类。

Java语言中是通过在类的声明中加入extends子句来创建一个类的子类的,其格式如下。

class SubClass extends SuperClass {

     ……

}

extends子句就是用来指明类之间的继承关系的。一个类是它的extends子句中指定的类的直接子类,而extends子句中指定的类又是这个类的直接父类。上例把SubClass声明为SuperClass的直接子类,如果SuperClass又是某个类的子类,则SubClass同时也是该类的(间接)子类。如果省略extends子句,则该类为java.lang.Object的子类。

子类可以继承父类中访问权限设定为publicprotectedfriendly的成员变量和方法。

从别的类继承下来的类叫做子类(subclass),那个被它继承的类叫做超类(superclass)。

4.1.1  属性和方法的继承

4-1  “圆”类继承“点”类图示

 

在面向对象技术中,继承是最为显著的一个特征。当一个类自动拥有另一个类的所有属性(域和方法)时,就称这两个类之间具有继承关系。被继承的类称为父类(或超类),继承了父类的类称为子类。

继承是一种由已有的类创建新类的机制。子类不仅可以从父类中继承域和方法,而且可以对这些域和方法重定义及扩充新的内容。一般方法是:创建一个共有属性的父类,再创建具有特殊属性的子类。例如将平面上的坐标点构成一个点类,则平面上的圆类可继承点类的所有属性,并以继承的坐标点为圆心,再自定义一个域为半径,完成圆类的各种操作,如图4-1所示。

一个父类可以同时拥有多个子类,此时该父类实际上是所有子类的公共域和方法的集合,而每一个子类则是对公共域和方法在功能、内涵方面的扩展和延伸。

父类、子类间的关系具有以下几个方面。

·    共享性:即子类可以共享父类的公共域和方法。

·    差异性:即子类和父类一定会存在某些差异,否则就应该是同一个类。

·    层次性:即由Java规定的单继承性,每个类都处于继承关系中的某一个层面。

下面以电话为例,图4-2列举了各种电话类的层次结构、域和方法。

4-2  电话及其子类的继承关系

从图4-2中可以看出,面向对象的程序设计中这种继承关系符合人们的日常思维方式。电话分为固定电话和移动电话两大类,固定电话可分为IP电话和普通电话等。其中,抽象的电话类是所有电话类的父类,它包含所有电话的公共属性。这些公共属性包括剩余金额等数据属性,以及计费方式、查询余额等行为属性。将电话分类并具体化,就分别派生出两个子类:固定电话和移动电话。这两个子类一方面继承了父类电话的所有属性,即也拥有剩余金额、计费方式、查询余额等属性,另一方面又专门定义了适合本身特殊需要的属性,如对于固定电话,应该有座机费属性,这些属性对移动电话不适合(这里假设移动电话没有座机费)。从固定电话到IP电话和普通电话的继承遵循相同的原则。

采用面向对象的设计方法,可以仅在抽象的父类——电话类中定义剩余金额等公共属性,其多个子类则从父类那里继承这些属性。当公共属性发生修改时,只需要在父类中修改一次即可。这样,不但维护工作量可以大大减少,而且可以避免修改遗漏引起的不一致性。

4.1.2  动态绑定

术语“绑定”或“方法查找”(Method Lookup),指的是这样一种动作,即对应于某一特定的调用选择执行哪个方法。在某些语言(如C)中,即使包含重载的方法,绑定也完全是在编译时完成。编译时完成的绑定也称为“静态绑定”,因为一旦设置了绑定,它在程序的执行期间就不会改变。然而,当包含类型强制转换和继承时,绑定就不能完全在编译时完成,因为调用方法时执行的实际代码体可能依赖于实际的对象的类,而这样的类在编译时可能还是未知的。当综合了类型强制转换、继承和方法覆盖时,就会出现动态绑定这种有意义的情况。

【例4-1  本例程序段给出了利用简单的类层次结构的两个例子,用来说明动态绑定的作用。

public class Base

{

        public void operation ()

}

public class Derived1 extends Base

{

    // does not override operation method

}

public class Derived2 extends Base

{

        public void operation ()

}

在这个类层次结构中,有两个类从同一个基类中派生出来。Base类和Derived2类包含了方法operation的定义。Derived1类并没有覆盖operation的定义。在operation方法被覆盖这种情况下,令人感兴趣的问题是:在涉及类型强制转换的不同条件下,应该执行哪一个方法?在下面的代码段中说明了这个问题。

Derived1 first=new Derived1() ;

Derived2 second= new Derived2() ;

Base generic;

generic=(Base)first ;

generic.operation();

generic=(Base)second;

generic.operation();

operation的第一次调用中,实际的对象属于类Derived1。由于Derived1类并没有覆盖operation方法,因此调用会引起执行Base类的operation方法。在operation的第二次调用中,实际的对象属于类Derived2,而Derived2确实覆盖了Base类中定义的operation方法。在后一种情况中,调用了两个operation方法中的哪一个呢?在Java中,总是基于对象的实际类型来执行被覆盖的方法。因此,在上面的代码段中,operation方法的第二次调用一定是Derived2类中的operation方法。因为对象的实际类型只有到运行时才能知道,因此将这种绑定称为动态的,以区别于可以编译时完全确定的那些绑定。动态绑定有时被称为对象“执行了正确的动作”,因为对象知道确切的类,并基于对其类的了解而将类绑定到其方法,尤其是当调用的代码并不知道目标对象的确切的类时。