1、第5章 继承与多态,5.1 继承 5.2 Java的继承 5.3 多态 5.4 Java的重载 5.5 构造函数的继承与重载 5.6 包 5.7 接口 5.8 小结 习题,本章讨论面向对象程序设计的另外两个重要特点:继承和多态。继承是面向对象程序设计方法中的一种重要手段,通过继承可以更有效地组织程序结构,明确类间关系,并充分利用已有的类来完成更复杂、深入的开发。多态则可以提高类的抽象度和封闭性,统一一个或多个相关类对外的接口。本章最后还将讨论接口和包。,5.1 继承 在面向对象技术的各个特点中,继承是最具有特色,也是与传统方法最不相同的一个。继承实际上是存在于面向对象程序中的两个类之间的一种关
2、系。当一个类获取另一个类中所有非私有的数据和操作的定义作为自己的部分或全部成分时,就称这两个类之间具有继承关系。被继承的类称为父类或超类,继承了父类或超类的所有数据和操作的类称为子类。,一个父类可以同时拥有多个子类,这时这个父类实际上是所有子类的公共域和公共方法的集合,而每一个子类则是父类的特殊化,是对公共域和方法在功能、内涵方面的扩展和延伸。现仍以电话卡为例,图5.1列举了各种电话卡类的层次结构、域和方法。,图 5.1 各种电话卡类及其间的继承关系,从图5.1可以看出,面向对象的继承关系很符合人们的日常思维模式。电话卡分为无卡号、有卡号两大类,无卡号的电话卡可以细分为磁卡、IC卡等,有卡号的
3、电话卡可分为IP电话卡和200电话卡等。其中,电话卡这个抽象概念对应的电话卡类是所有其他类的父类,它是所有电话卡的公共属性的集合。这些公共属性包括卡中剩余金额等静态的数据属性,以及拨打电话、查询余额等动态的行为属性。将电话卡具体化、特殊化,就分别派生出两个子类:无卡号电话卡和有卡号电话卡。,这两个子类一方面继承了父类电话卡的所有属性(包括域与方法),即它们也拥有剩余金额、拨打电话、查询余额等数据和操作,另一方面它们又根据自己对原有的父类概念的明确和限定,专门定义了适用于本类特殊需要的特殊属性,如,对于所有的有卡号电话卡,应该有卡号、密码、接入号码等域和登录交换机的行为,这些属性对无卡号电话卡是
4、不适合的。从有卡号电话卡到IP电话卡和200电话卡的继承遵循完全相同的原则。,使用继承的主要优点,是使得程序结构清晰,降低编码和维护的工作量。仍以图5.1为例。剩余金额是所有电话卡共有的属性,第一种实现方案是为每一个电话卡类中都定义自己的剩余金额域;第二种实现方案是仅在抽象的电话卡父类中定义剩余金额域,其他类则从它那里继承。因此第一种方案相对于第二种方案,代码量要多出若干倍。同时,当公共属性发生修改时,第一种方案需要在每个类中做相应的修改,而第二种方案只需要在父类中修改一次即可,不但维护的工作量大大减少,而且也避免了在第一种方案中可能出现的修改遗漏。,在面向对象的继承特性中,还有一个关于单重继
5、承和多重继承的概念。所谓单重继承,是指任何一个类都只有一个单一的父类;而多重继承是指一个类可以有一个以上的父类,它的静态的数据属性和操作从所有这些父类中继承。采用单重继承的程序结构比较简单,如图5.1所示的是单纯的树状结构,掌握、控制起来相对容易;而支持多重继承的程序,其结构则是复杂的网状,设计、实现都比较复杂。但是现实世界的实际问题,它们的内部结构多为复杂的网状,用多重继承的程序模拟起来比较自然,而单重继承的程序要解决这些问题,则需要其他的一些辅助措施。C+是开发人员熟悉的支持多重继承的面向对象的编程语言,而本书中介绍的Java语言,出于安全、可靠性的考虑,仅支持单重继承。,综上所述,在面向
6、对象的程序设计中,采用继承的机制来组织、设计系统中的类,可以提高程序的抽象程度,使之更接近于人类的思维方式,同时也可以提高程序开发效率,降低维护的工作量。,5.2 Java的继承 5.2.1 派生子类 Java中的继承是通过extends关键字来实现的,在定义类时使用extends关键字指明新定义类的父类,就在两个类之间建立了继承关系。新定义的类称为子类,它可以从父类那里继承所有非private的属性和方法作为自己的成员。 例 5-1 实现图5.1中电话卡类的继承结构。,1: abstract class PhoneCard2: 3: double balance;4:5: abstract
7、boolean performDial( );6: double getBalance( )7: 8: return balance;9: 10: 11: abstract class None-Number-PhoneCard extends PhoneCard12: 13: String phoneSetType;,14:15: String getSetType( )16: 17: return phoneSetType;18: 19: 20: abstract class Number-PhoneCard extends PhoneCard21: 22: long cardNumber
8、;23: int password;24: String connectNumber;25: boolean connected;26:,27: boolean performConnection(long cn,int pw)28: 29: if(cn = cardNumber 36: 37: 38: class magCard extends None-Number-PhoneCard39: ,40: String usefulArea;41:42: boolean performDial( )43: 44: if( balance 0.9)45: 46: balance -= 0.9;4
9、7: return true;48: 49: else50: return false;51: 52: ,53: class IC-Card extends None-Number-PhoneCard54: 55: boolean performDial( )56: 57: if( balance 0.5)58: 59: balance -= 0.9;60: return true;61: 62: else63: return false;64: 65: ,66: class IP-Card extends Number-PhoneCard67: 68: Date expireDate;69:
10、 boolean performDial( )70: 71: if( balance 0.3 78: ,79: 80: class D200-Card extends Number-PhoneCard81: 82: double additoryFee;83: 84: boolean performDial( )85: 86: if( balance (0.5 + additoryFee )87: 88: balance -= (0.5 + additoryFee);89: return true;90: 91: else,92: return false;93: 94: 例5-1定义了Pho
11、neCard,None-Number-PhoneCard,Number-PhoneCard,magCard,IC-Card,IP-Card,D200-Card共七个类,其中None-Number-PhoneCard类和Number-PhoneCard类是PhoneCard类派生出的子类;magCard类和IC-Card类是None-Number-PhoneCard类派生出的子类;IP-Card类和D200-Card类是Number-PhoneCard类派生出的子类。,可以注意到,例5-1的程序中只有在第3句(PhoneCard类中)定义了域balance,但是在第44,46(magCard类中
12、),第57,59(IC-Card类中),第71,73(IP-Card类中),第86,88(D200-Card类中)句中都使用了balance域,它们自身并未定义balance域,使用的balance都是从父类PhoneCard那里继承来的。另外,PhoneCard类在第5句定义了一个抽象方法performDial( ),它的两个子类也是抽象类,可以不实现这个抽象方法,分别派生出来的4个电话卡类不是抽象类,故而分别定义了针对自己具体情况的performDial( )方法。例如磁卡电话通话时没有优惠时段,平均话费较高;200卡的话费较低,但是有额外的附加费;IP卡的话费最低,但是必须在失效日期之前
13、拨打电话。,最后,第68句使用一个java.util包中的Java系统类Date,每个Date类的对象代表一个具体的日期。第71句中new Date( )表达式的作用是创建一个包含当前日期的Date类的对象,after( )方法是Date类的方法,在失效日期比当前日期晚时,expireDate.after(new Date( )返回true,否则返回false。,5.2.2 域的继承与隐藏 1. 域的继承 子类可以继承父类的所有非私有域。例如各类电话卡类所包含的域分别为:PhoneCard类:double balance;None-Number-PhoneCard类:double balanc
14、e; /继承自父类PhoneCardString phoneSetType;Number-PhoneCard类:double balance; /继承自父类PhoneCardlong cardNumber;,int password;String connectNumber;boolean connect;magCard类:double balance; /继承自父类None-Number-PhoneCardString phoneSetType; /继承自父类None-Number- PhoneCardString usefulArea;IC-Card类:double balance; /继
15、承自父类None-Number-PhoneCardString phoneSetType; /继承自父类None-Number- PhoneCardIP-Card类:,double balance; /继承自父类Number-PhoneCardlong cardNumber; /继承自父类Number-PhoneCardint password; /继承自父类Number-PhoneCardString connectNumber; /继承自父类Number-PhoneCardboolean connect; /继承自父类Number-PhoneCardDate expireDate;D200
16、-Card类:double balance; /继承自父类Number-PhoneCardlong cardNumber; /继承自父类Number-PhoneCardint password; /继承自父类Number-PhoneCardString connectNumber; /继承自父类Number-PhoneCardboolean connect; /继承自父类Number-PhoneCarddouble additoryFee;,可见父类的所有非私有域实际是各子类都拥有的域的集合。子类从父类继承域而不是把父类域的定义部分复制一遍,这样做的好处是减少程序维护的工作量。 2. 域的隐藏
17、 子类重新定义一个与从父类那里继承来的域变量完全相同的变量,称为域的隐藏。 例如,如果把例5-1中第80到94句定义的D200-Card方法修改为: 80: class D200-Card extends Number-PhoneCard81: 82: double additoryFee;,83: double balance;84: boolean performDial( )85: 86: if( balance (0.5 + additoryFee )87: 88: balance -= (0.5 + additoryFee);89: return true;90: 91: else9
18、2: return false;93: 94:,在第83句增加定义了一个与从父类那里继承来的balance变量完全相同的变量。这样修改后,D200-Card类中的域变为:D200-Card类:double balance; /继承自父类Number-PhoneCarddouble balance; /D200-Card类自己定义的域long cardNumber; /继承自父类Number-PhoneCardint password; /继承自父类Number-PhoneCardString connectNumber; /继承自父类Number-PhoneCardboolean connec
19、t; /继承自父类Number-PhoneCarddouble additoryFee;,这时,子类中定义了与父类同名的属性变量,即出现了子类变量对同名父类变量的隐藏。这里所谓隐藏是指子类拥有了两个相同名字的变量,一个继承自父类,另一个由自己定义;当子类执行继承自父类的操作时,处理的是继承自父类的变量,而当子类执行它自己定义的方法时,所操作的就是它自己定义的变量,而把继承自父类的变量“隐藏”起来。参看下面的例5-2。 例 5-2 TestHiddenField.java,1: public class TestHiddenField2: 3: public static void main(S
20、tring args)4: 5: D200-Card my200 = new D200-Card( );6: my200.balance = 50.0;7: System.out.println(父类被隐藏的金额为: +my200.getBalance( );8:if(my200.performDial( )9: System.out.println(子类的剩余金额为: +my200.balance);10: 11:12:abstract class PhoneCard13: ,14: double balance;15:16: abstract boolean performDial( );
21、17: double getBalance( )18: 19: return balance;20: 21: 22:abstract class Number-PhoneCard extends PhoneCard23: 24: long cardNumber;25: int password;26: String connectNumber;,27: boolean connected;28: 29: boolean performConnection(long cn,int pw)30: 31: if(cn = cardNumber 38: 39: ,40: class D200-Card
22、 extends Number-PhoneCard41: 42: double additoryFee;43: double balance;44: 45: boolean performDial( )46: 47: if( balance (0.5 + additoryFee )48: 49: balance -= (0.5 + additoryFee);50: return true;51: 52: else,53: return false;54: 55: 图5.2是例5-2的运行结果。,图 5.2 例5-2的运行结果,例5-2中,第5句创建了一个D200-Card类的对象my200,这
23、个对象有两个balance变量,一个继承自父类PhoneCard,另一个是在第43句中重新定义的自身的balance变量。第6句为my200对象的balance变量赋值,根据域隐藏的原则,这里是为my200自身的balance变量赋值。第7句输出my200对象的getBalance( )方法的返回值,这里的getBalance( )方法是在父类PhoneCard中定义的,它返回的是my200对象继承自父类PhoneCard的balance变量的数值,这个balance没有被赋值,其数值是缺省的0.0。第8句调用my200对象的performDial( )方法拨打电话,修改my200对象自身的b
24、alance变量。第9句输出拨打电话之后,my200对象的balance变量的数值。,5.2.3 方法的继承与隐藏 1. 方法的继承 父类的非私有方法作为类的非私有成员,也可以被子类所继承。如例5-2中第7句调用的my200对象的getBalance( )方法就继承自父类PhoneCard类。根据方法的继承关系,列举各种电话卡所包含方法(仅列出方法头)如下: PhoneCard类:abstract boolean performDial( );double getBalance( )None-Number-PhoneCard类:abstract boolean performDial( );/
25、继承自父类PhoneCard,double getBalance( ) /继承自父类PhoneCardString getSetType( )Number-PhoneCard类:abstract boolean performDial( ); /继承自父类PhoneCarddouble getBalance( ) /继承自父类PhoneCardboolean performConnection(long cn,int pw)magCard类:double getBalance( ) /继承自父类None-Number- PhoneCardString getSetType( ) /继承自父类N
26、one-Number- PhoneCardboolean performDial( )IC-Card类:,double getBalance( ) /继承自父类None-Number- PhoneCardString getSetType( ) /继承自父类None-Number- PhoneCardboolean performDial( )IP-Card类:boolean performDial( )double getBalance( ) /继承自父类Number-PhoneCardboolean performConnection(long cn,int pw)/继承自父类Number
27、-PhoneCardD200-Card类:boolean performDial( )double getBalance( ) /继承自父类Number-PhoneCard,boolean performConnection(long cn,int pw)/继承自父类Number-PhoneCard 各类的对象可以自由使用从父类那里继承来的方法。,2. 方法的覆盖 正像子类可以定义与父类同名的域,实现对父类域变量的隐藏一样;子类也可以重新定义与父类同名的方法,实现对父类方法的覆盖(Overload)。方法的覆盖与域的隐藏的不同之处在于:子类隐藏父类的域只是使之不可见,父类的同名域在子类对象中仍
28、然占有自己的独立的内存空间;而子类方法对父类同名方法的覆盖将清除父类方法占用的内存,从而使父类方法在子类对象中不复存在。例如上一小节列举的各电话卡类的方法中,magCard,IC-Card,IP-Card,D200-Card四个类都定义了自己的performDial( )方法,所以它们从父类那里继承来的抽象的performDial( )就不存在了。,在例5-2的D200-Card类中增加定义一个与从父类那里继承来的getBalance( )方法同名的方法,就可得到例5-3。其运行结果如图5.3所示,可见此时调用子类对象my200自己的getBalance( )方法,返回的是my200对象自己的
29、balance域。,图 5.3 例5-3的运行结果,例 5-3 TestOverLoad.java1: public class TestHiddenField2: 3: public static void main(String args)4:5: D200-Card my200 = new D200-Card( );6: my200.balance = 50.0;7: System.out.println(父类被隐藏的金额为: +my200.getBalance( );8:if(my200.performDial( )9: System.out.println(子类的剩余金额为: +my
30、200.balance);10: 11: ,12: abstract class PhoneCard13: 14: double balance;15:16: abstract boolean performDial( );17: double getBalance( )18: 19: return balance;20: 21: 22: abstract class Number-PhoneCard extends PhoneCard23: 24: long cardNumber;,25: int password;26: String connectNumber;27: boolean c
31、onnected;28: 29: boolean performConnection(long cn,int pw)30: 31: if(cn = cardNumber ,38: 39: 40: class D200-Card extends Number-PhoneCard41: 42: double additoryFee;43: double balance;44: 45: boolean performDial( )46: 47: if( balance (0.5 + additoryFee )48: 49: balance -= (0.5 + additoryFee);50: ret
32、urn true;,51: 52: else53: return false;54: 55: double getBalance( )56: 57: return balance;58: 59: ,5.2.4 this与super this和super是常用来指代父类对象和子类对象的关键字。Java系统默认,每个类都缺省地具有null,this和super三个域,所以在任意类中都可以不加说明而直接使用它们。其中null代表“空”, 代表一个什么也没有的“空”值。在定义一个对象但尚未为其开辟内存单元时可以指定这个对象为null。this和super两个域则与继承有密切关系。,1. this th
33、is表示的是当前对象本身,更准确地说,this代表了当前对象的一个引用。对象的引用可以理解为对象的另一个名字,通过引用可以顺利地访问到对象,包括访问、修改对象的域、调用对象的方法。这一点有点像C/C+语言中的指针,但是对象的引用与内存地址无关,它仅仅是对象的另一个名字。 一个对象可以有若干个引用,this就是其中之一。利用this可以调用当前对象的方法或使用当前对象的域。例如,在D200-Card类中的getBalance ( )方法需要访问同一个对象的域balance,可以利用this写成:,double getBalance( )return this.balance; 表示返回的是当前同
34、一个对象的balance域,当然在这种情况下this也可以不加。更多的情况下,this用来把当前对象的引用作为参数传递给其他的对象或方法。例如,图形界面的Java Applet程序(例5-4): 例5-4 getDouble.java,1: import java.applet.*;2: import java.awt.*;3: import java.awt.event.*;4:5: public class getDouble extends Applet implements ActionListener6: 7: Label prompt;8: TextField input;9: d
35、ouble d = 0.0;10:11: public void init( )12: 13: prompt = new Label(请输入一个浮点数:);,14: input = new TextField(10);15: add(prompt);16: add(input);17: input.addActionListener(this);18: 19: public void paint(Graphics g)20: 21: g.drawString(你输入了数据: + d,10,50);22: 23: public void actionPerformed(ActionEvent e
36、)24: 25: d = Double.valueOf(input.getText( ).doubleValue( );,26: repaint( );27: 28: 例5-4中,第17句调用的addActionListener( )方法是系统类TextField的方法,调用这个方法要求提供一个实现了ActionListener接口的对象作为实际参数。第5句中定义的用户类getDouble利用implements关键字(接口及其实现将在本章后面介绍)实现了ActionListener接口,就使用this将当前getDouble类的对象指定为调用addActionListener( )方法的实际
37、参数。,2. super super表示的是当前对象的直接父类对象,是当前对象的直接父类对象的引用。所谓直接父类是相对于当前对象的其他“祖先”类而言的。例如,假设类A派生出子类B,B类又派生出自己的子类C,则B是C的直接父类,而A是C的祖先类。同理,Number-PhoneCard类是D200-Card类的直接父类,PhoneCard类是D200-Card类的祖先类。super代表的就是直接父类。 例 5-5 TestSuper.java,1: public class TestHiddenField2: 3: public static void main(String args)4:5:
38、D200-Card my200 = new D200-Card( );6: my200.balance = 50.0;7: System.out.println(父类被隐藏的金额为: +my200.getBalance( );8:if(my200.performDial( )9: System.out.println(子类的剩余金额为: +my200.balance);10: 11: 12: abstract class PhoneCard,13: 14: double balance;15:16: abstract boolean performDial( );17: double getB
39、alance( )18: 19: return balance;20: 21: 22: abstract class Number-PhoneCard extends PhoneCard23: 24: long cardNumber;25: int password;,26: String connectNumber;27: boolean connected;28: 29: boolean performConnection(long cn,int pw)30: 31: if(cn = cardNumber 38: ,39: 40: class D200-Card extends Numbe
40、r-PhoneCard41: 42: double additoryFee;43: double balance;44: 45: boolean performDial( )46: 47: if( balance (0.5 + additoryFee )48: 49: balance -= (0.5 + additoryFee);50: return true;51: ,52: else53: return false;54: 55: double getBalance( )56: 57: return super.balance;58: 59: 图5.4是例5-5的运行结果。,图 5.4 例
41、5-5的运行结果,例5-5中,第57句D200-Card类的方法getBalance( )返回的是当前对象的super域的balance变量,当前对象的super域是D200-Card类的直接父类Number-PhoneCard的引用,Number-PhoneCard的balance变量是从PhoneCard类那里继承来的。所以例5-5中,即使调用子类的getBalance( )方法,返回的仍是没有赋值的父类的balance变量。 这里需要注意的是:this和super是属于类的有特指的域,只能用来代表当前对象和当前对象的父对象,而不能像其他类的属性一样随意引用。下面语句中的用法都是错误的。,
42、D200-Card my200 = new D200-Card( ) ;my200 . this . getBalance ( ) ; / 错误my200 . super . getBalance ( ) ; / 错误 除了用来指代当前对象或父类对象的引用外,this和super还有一个重要的用法,就是调用当前对象或父类对象的构造函数。这部分内容将在下面介绍。,3. 父类对象与子类对象的转换 类似于基本数据类型数据之间的强制类型转换,存在继承关系的父类对象和子类对象之间也可以在一定条件下相互转换。父类对象和子类对象的转化需要注意如下原则: (1) 子类对象可以被视为是其父类的一个对象。 (2)
43、 父类对象不能被当作是其某一个子类的对象。 (3) 如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数。,(4) 如果父类对象引用指向的实际是一个子类对象(在以前的某个时候根据(1)把子类对象的引用赋值给这个父类对象的引用),那么这个父类对象的引用可以用强制类型转换转化成子类对象的引用。 参看下面的程序片断:class SuperClass /定义父类int x;class SubClass extends SuperClass /定义子类,int y;char ch;public class UseSuperSub /使用父类与子类SuperClass
44、sc, sc-ref;SubClass sb, sb-ref;sc = new SuperClass( );sb = new SubClass( );sc-ref = sb; /父类引用可以指向子类对象sb-ref = (SubClass)sc-ref; /父类引用转换成子类引用,5.3 多态 多态是面向对象程序设计的又一个特殊特性。我们已经知道,利用面向过程的语言编程,主要工作是编写一个个过程或函数。这些过程和函数各自对应一定的功能,它们之间是不能重名的,否则在用名字调用时,就会产生歧异和错误。,而在面向对象的程序设计中,有时却需要利用这样的“重名”现象来提高程序的抽象度和简洁性。考察图5.
45、1中的电话卡结构树,“拨打电话”是所有电话卡都具有的操作,但是不同的电话卡“拨打电话”操作的具体实现是不同的。如磁卡的“拨打电话”是“找到磁卡电话机直接拨号”,200卡的“拨打电话”是“找到双音频电话机,先输入卡号、密码后再拨号”。如果不允许这些目标和最终功能相同的程序用同样的名字,就必须分别定义“磁卡拨打电话”、“200卡拨打电话”等多个方法。这样一来,继承的优势就荡然无存了。在面向对象的程序设计中,为了解决这个问题,引入了多态的概念。,所谓多态,是指一个程序中同名的不同方法共存的情况。面向对象的程序中多态的情况有多种,可以通过子类对父类方法的覆盖实现多态,也可以利用重载在同一个类中定义多个
46、同名的不同方法。 首先考察第一种多态,仍以各类电话卡为例。PhoneCard类有一个各子类共有的方法“拨打电话”,代表拨打电话的功能。根据继承的特点,PhoneCard类的每一个子类都将继承这个方法。但是,这个方法代表的相同功能在不同种类的电话卡中,其具体实现是不同的。为了体现这一点,不同的子类可以重新定义、编写“拨打电话”这个方法(performDial( )的内容,以满足本类的特殊需要,实现这个具体电话卡的特定拨打电话的方法。,例如,磁卡类可以重新定义“拨打电话”方法,用“找到磁卡电话机直接拨号”来实现它;200卡类也可以重新定义继承自PhoneCard的“拨打电话”方法,“找到双音频电话
47、机,先输入卡号、密码后再拨号”是它的具体内容所有的类中,凡是实现拨打电话这种功能的方法,虽然内容不同,但却共享相同的名字“performDial”。这种子类对继承自父类的方法的重新定义,就称为方法的覆盖(overload),是一种很重要的多态的形式。,多态情况下进行方法调用时,如何区分这些同名的不同方法呢?在覆盖多态中,由于同名的不同方法是存在于不同的类(如磁卡、IP卡、200卡等)中的,所以只需在调用方法时指明调用的是哪个类的方法,就可以很容易地把它们区分开来,如“MymagCard.performDial( )”(MymagCard是magCard类的对象)或“my200.performDial( )”。另外还有一种不明确指定具体子类而把区分工作留给系统的情况。比如设某业务员需要向总部汇报一条消息,总部指示他:用手机或电话卡拨打电话。至于业务员使用何种电话卡,具体如何拨打电话这些细节则不需要总部了解,系统会自动分辨所使用的电话卡是哪种类型的电话卡,并调用相应的拨打电话的具体方法。,