第5章继承与多态.docx
- 文档编号:8469719
- 上传时间:2023-01-31
- 格式:DOCX
- 页数:19
- 大小:76.19KB
第5章继承与多态.docx
《第5章继承与多态.docx》由会员分享,可在线阅读,更多相关《第5章继承与多态.docx(19页珍藏版)》请在冰豆网上搜索。
第5章继承与多态
第5章继承与多态
本章讨论面向对象程序设计另外两个最重要的特点:
继承和多态。
继承是面向对象程序设计方法中实现软件重用的一种重要手段,通过继承可以更有效地组织程序结构,明确类之间的关系,并充分利用已有的类来创建新类,通过继承可以实现代码的重用,以完成更复杂的设计、开发。
多态则可以统一多个相关类的对外接口,并在运行时根据不同的情况执行不同的操作,提高类的抽象度和灵活性。
5.1子类、父类与继承机制
5.1.1继承的概念
在类的定义过程中,继承是一种由已有的类创建新类的机制。
继承而得到的类为子类,被继承的类为父类,父类包括所有直接或间接被继承的类。
在类的声明中加入extends子句就可以创建一个类的子类:
classSubClassextendsSuperClass{……}
Java中不通过extends关键字支持多重继承,即extends之后只能有唯一的父类。
如果缺省extends子句,则该类为java.lang.Object的子类。
子类可以继承父类中访问权限设定为public、protected和包可访问的成员变量和方法,但是不能继承访问权限为private的成员变量和方法。
一个父类可以同时拥有多个子类,而每一个子类则只能有唯一的父类。
子类是对公共域和方法在功能、内涵方面的扩展和延伸。
父类、子类间的关系具有:
共享性即子类可以共享父类的公共域和方法。
差异性即子类和父类一定会存在某些差异,否则就应该是同一个类。
层次性即由Java规定的单继承性,每个类都处于继承关系中的某一个层面。
5.1.2类的层次
Java语言中类的是有严格的层次结构的。
除Object类之外,每个类都继承惟一的父类。
这种继承性性质使得类的层次结构形成了如图所示的一种树状结构。
Object类定义和实现了Java系统所需要的众多类的共同行为,它是所有类的父类,也即这个树状结构中的根类,所有的类都是由这个类继承、扩充而来的,这个Object类定义在java.lang包中。
5.2继承的实现
5.2.1子类的定义
定义一个子类,即在定义一个类的时候加上extends关键字,并在之后带上其父类名,其一般格式为:
[类的修饰符]class<子类名>extends<父类名>{
<域定义>;
<方法定义>;
}
这和前面定义类的格式并没有什么区别,只是在定义子类时“extends<父类名>”不再是可选项。
何时选择继承?
一个很好的经验:
“B是A中的一类吗?
”
如果是则让B做A的子类.
classPoint{
intx,y;
voidsetXY(inti,intj){
x=i;y=j;
}
}
classCircleextendsPoint{
doubler;
doublearea(){
return3.14*r*r;
}
}
电话及其子类的继承关系
【例5.2】实现电话类的继承结构。
为了代码的简洁,而把注意力集中在我们要讨论的语法机制上,这里假设移动电话仅一种,电话的计费方式为:
国内长途和国际长途话费分别是市话费的3倍和9倍。
abstractclassTelephone{
longphoneNumber;
finalintlocal_Call=1;
finalintdistance_Call=3;
finalintinternational_Call=9;
doublebalance;
abstractbooleancharge_Mode(intcall_Mode);
doublegetBalance(){returnbalance;}
}
5.2.2域的继承与隐藏
1.域的继承
子类可以继承父类的所有非私有域。
例如各类电话类所包含的域分别为:
Mobile_Phone类
longphoneNumber;来自继承父类Telephone的5个基本域
intlocal_Call;
intdistance_Call;
intinternational_Call
doublebalance;
StringnetworkType;
IP_Phone类
longphoneNumber;来自继承父类Telephone的5个基本域
intlocal_Call;
intdistance_Call;
intinternational_Call
doublebalance;
doublemonthFee来自继承父类Fixed_Telephone
boolearnstarted;
DateexpireDate;子类IP_Phone类中声明的域
Ordinary_phone类
longphoneNumber;来自继承父类Telephone的5个基本域
intlocal_Call;
intdistance_Call;
intinternational_Call
doublebalance;
doublemonthFee;来自继承父类Fixed_Telephone
booleanlongdistanceService;子类Ordinary_phone类中声明的域
booleaninternationalService;
2.域的隐藏
子类重新定义一个与从父类继承来的域变量完全相同的变量,称为域的隐藏。
即子类中定义了与父类同名的域变量,就是子类变量对同名父类变量的隐藏。
这里所谓隐藏是指子类拥有了两个相同名字的变量,一个来自继承父类,另一个由自己定义。
在这种情况下,当子类执行继承的父类方法时,处理的是父类的变量,而当子类执行它自己定义的方法时,所操作的就是它自定义的变量,而把来自继承父类的变量“隐藏”起来了。
classAclassBextendsA
{staticintdata_a=3;{staticintdata_a=5;
}}
classCextendsB
{voidprint_out()
{System.out.println(“data_a=”+data_a);
System.out.println(“A.data_a=”+A.data_a);
System.out.println(“B.data_a=”+B.data_a);
}
}
classdemo
{publicstaticvoidmain(Stringargs[])
{
Cc=newC();
c.print_out();
}
}
data_a=5
A.data_a=3
B.data_a=5
❿隐藏父类的剩余金额
5.2.3方法的继承与覆盖
1.方法的继承
父类的非私有方法可以被子类所继承。
根据方法的继承关系,将例5.3中电话类及其各子类所包含的方法列举如下。
(1)Telephone类
abstractbooleancharge_Mode();
doublegetBalance();
(2)Fixed_Telephone类
abstractbooleancharge_Mode();//来自继承父类Telephone
doublegetBalance();//来自继承父类Telephone
(3)Ordinary_phone类
booleancharge_Mode();
doublegetBelance();//来自继承父类Fixed_Telephone
2.方法的覆盖
方法的覆盖(Override)是指子类重定义从父类继承来的一个同名方法,此时父类和子类中都存在一个同名方法,父类这个方法在子类中不复存在。
这是子类通过重新定义与父类同名的方法,实现自身的行为。
方法覆盖时应遵循的原则:
(1)覆盖后的方法不能比被覆盖的方法有更严格的访问权限。
(2)覆盖后的方法不能比被覆盖的方法产生更多的异常。
classCar
{intcolor_number;
intdoor_number;
intspeed;
publicvoidpush_break()
{
speed=0;
}
publicvoidadd_oil(){…}
}
classTrash_CarextendsCar
{
doubleamount;
publicvoidfill_trash(){…}
publicvoidpush_break()
{
speed=speed–10;
}
}
覆盖方法的调用
对于重写的方法,Java运行时系统根据调用该方法的实例的类型来决定选择哪个方法调用。
publicclassDemoCar
{publicstaticvoidmain(Stringargs[])
{CaraCar=newTrash_Car();
aCar.push_break();
}
}
在这里,aCar.push_break()调用的是类Trash_Car中的push_break()方法将被调用。
成员变量的隐藏和方法的覆盖的意义在于:
通过隐藏域和覆盖方法可以把父类的状态和行为改为自身的状态和行为,对外统一名字与接口,又不失其继承性。
5.3多态性
多态性是面向对象程序设计的又一个重要的技术和手段。
多态性是指同名的不同方法在程序中共存。
即为同一个方法名定义几个版本的实现,运行时根据不同情况执行不同的版本。
调用者只需使用同一个方法名,系统会根据不同情况,调用相应的不同方法,从而实现不同的功能。
多态性又被称为“一个名字,多个方法”。
5.3.1多态性的概念
多态性的实现有两种方式:
(1)覆盖实现多态性
通过子类对继承父类方法的重定义来实现。
使用时注意:
在子类重定义父类方法时,要求与父类中方法的原型(参数个数、类型、顺序)完全相同。
(2)重载实现多态性
在一个类中的定义多个同名方法的不同实现。
定义方法时方法名相同,但方法的参数不同(参数的个数、类型、顺序不同)。
这些方法同名的原因是具有类似的功能且目的相同,但在实现该功能的具体方式和细节方面有所不同,因此需要定义多种不同的方法体
5.3.2覆盖实现多态性
在覆盖实现多态性的方式中,子类重定义父类方法,此时方法的名字、参数个数、类型、顺序完全相同,那么如何区别这些同名的不同方法呢?
此时这些方法是存在不同的类层次结构中,在调用方法时只需要指明是调用哪个类(或对象)的方法,就很容易把它们区分开来,其调用形式为:
对象名.方法名或类名.方法名
例如,IP电话的计费,若建立IP_Phone类的对象my,其调用为:
my.charge_Mode();
假如charge_Mode()是一个类方法,则可使用类名,其调用为:
IP_Phone.charge_Mode();例5.5
5.3.3重载实现多态性
若通过重载来实现多态性,则是在同一个类中定义多个同名方法。
由于重载发生在同一个类中,不能再用类名或对象名来区分不同的方法了,所以在重载中采用的区分方法是使用不同的形式参数表,包括形式参数的个数不同、类型不同或顺序的不同。
例如,在重载加法add(x,y)时,整数加法的形参类型是整型,复数加法的形参类型是复数型。
根据填入的参数的类型或者参量等的不同,系统可以确定调用哪一个加法函数来完成加法计算。
例5.6ReloadExample.java
5.3.4多态性举例
多态性还可以是指在程序中需要使用父类对象的地方,都可以用子类对象来代替。
例如:
publicclassEmployeeextendsObject
{……}
publicclassManagerextendsEmployee
{……}
则:
Employeee=newManager();//合法语句
5.3.4多态性举例
对象状态的确定
在Java语言中,提供了操作符instanceof用来判断对象是否属于某个类的实例。
publicvoidmethod(Employeee){
if(einstanceofManager){
…//dosomethingasaManager
}
elseif(einstanceofContractor){
…//dosomethingasaContractor
}
else{
…//dosomethingelse
}
}
5.4构造方法的继承与重载
5.4.1构造方法的重载
构造方法的重载是指同一个类中定义不同参数的多个构造方法,以完成不同情况下对象的初始化。
例如,point类可定义不同的构造方法创建不同的点对象。
point();//未初始化坐标
point(x);//初始化一个坐标
point(x,y);//初始化两个坐标
一个类的若干个构造方法之间可以相互调用。
当类中一个构造方法需要调用另一个构造方法时,可以使用关键字this,并且这个调用语句应该是该构造方法的第一个可执行语句。
【例5.7】对Ordinary_phone类,可以根据需要定义几个构造方法:
Ordinary_phone(){}
//无参数的构造方法,对象的各域置为默认初始值
Ordinary_phone(booleandisService){
this();//调自身的无参数的构造方法
longdistanceService=disService;
//确定是否开通国内长途电话
}
Ordinary_phone(booleandisService,booleanintService){this(disService);//调自身的带一个参数的构造方法
internationalService=intService;//确定是否开通国际长途
}
Ordinary_phone(booleandisService,booleanintService,doubleb){
//3个参数的构造方法
this(disService,intService);
//调自身的带两个参数的构造方法
balance=b;//设置话费金额
}
使用this域来调用同类的其他构造方法,其优点是可以最大限度地提高对已有代码的利用程度,提高程序的抽象、封装程度,以及减少程序维护的工作量。
5.4.2构造方法的继承
子类可以继承父类的构造方法,继承的方式遵循以下原则:
(1)子类无条件地继承父类的无参数的构造方法。
(2)如果子类没有定义构造方法,则它将继承父类的无参数构造方法作为自己的构造方法;如果子类定义了构造方法,则在创建新对象时,将先执行来自继承父类的无参数构造方法,然后再执行自己的构造方法。
(3)对于父类的带参数构造方法,子类可以通过在自己的构造方法中使用super关键字来调用它,但这个调用语句必须是子类构造方法的第一个可执行语句。
下面我们主要讨论第2和第3点。
1.父类与子类的构造方法的执行顺序
下面给出一个简单示意性的例子。
【例5.7】试分析下面程序的继承关系以及构造方法的调用顺序。
我们是否可以先不看执行结果,分析构造方法执行顺序是A()、B()、C()还是C()、B()、A()?
ØConstructorTest.java
该例表明:
在创建新对象时,子类先执行来自继承父类的无参数构造方法,然后再执行自己的构造方法。
2.在构造方法中super关键字的使用
Fixed_Telephone(){}//无参数的构造方法
Fixed_Telephone(longpn){
phoneNumber=pn;//初始化电话号码
}
Fixed_Telephone(longpn,doublemf){
phoneNumber=pn;
monthFee=mf;//初始化座机费
}
Fixed_Telephone(longpn,doublemf,doubleb){
phoneNumber=pn;
monthFee=mf;
balance=b;//初始化电话费的剩余金额
}
super是表示父类对象的关键字,super表示当前对象的直接父类对象的一个引用,通过super可使用父类对象的方法或域。
Fixed_Telephone(){}//无参数的构造方法
Fixed_Telephone(longpn){
phoneNumber=pn;//初始化电话号码
}
Fixed_Telephone(longpn,doublemf){
phoneNumber=pn;
monthFee=mf;//初始化座机费
}
Fixed_Telephone(longpn,doublemf,doubleb){
phoneNumber=pn;
monthFee=mf;
balance=b;//初始化电话费的剩余金额
}
Ordinary_phone(longpn,doublemf,booleands){
super(pn,mf);
longdistanceService=ds;
}
Ordinary_phone(longpn,doublemf,doubleb,booleands){
super(pn,mf,b);//调用父类三个参数的构造方法为继承的域赋初值
longdistanceService=ds;//用参数初始化自定义域
}
设计子类的构造方法可选择如下方式:
(1)仅调用父类的无参数构造方法。
(2)定义自己的一个(或多个)构造方法,并调用父类的带参数的构造方法。
5.4.3构造方法的综合举例
构造方法的继承与重载以及方法的覆盖的综合举例。
【例5.8】
本例中使用重载技术定义了Fixed_PhoneCsrd类的4个构造方法;使用继承和重载技术定义了Ordinary_phone类的两个构造方法;使用覆盖技术在Ordinary_phone类中覆盖了父类的getBalance()方法以及Object类的toString()方法。
主类中创建类的对象myHomePhone时使用了第二个构造方法,并对大部分的域都进行初始化,
接口(interface)也有人翻译为界面,是用来实现类间多重继承功能的一种结构。
接口是在语法上与类有些相似。
它定义了若干个抽象方法和常量,形成一个属性集合,该属性集合通常对应了某一组功能。
凡是需要实现这种特定功能的类,都可以继承并使用它。
一个类只能直接继承一个父类,但可以同时实现若干个接口。
实现(或继承)接口实际上就获得了多个特殊父类的属性,即实现了多重继承。
所谓多重继承,是指一个子类可以有一个以上的直接父类,该子类可以继承它所有父类的属性。
5.5.1接口与多继承
接口定义的仅仅是实现某一特定功能的一组对外的协议和规范,而并没有真正地实现这个功能。
这些功能的真正实现是在继承这个接口的各个类中完成的。
因为接口包含的是未实现的一些抽象的方法,它与抽象类有些相象。
它们之间存在以下的区别:
接口不能有任何实现了的方法,而抽象类可以。
类可以继承(实现)许多接口,但只能继承一个父类。
类有严格的层次结构,而接口没有层次结构,没有联系的类可以实现相同的接口。
5.5.2接口的定义
接口是由常量和抽象方法组成的特殊类。
接口的定义包括两个部分:
接口声明和接口体。
声明接口一般格式如下:
[public]interface接口名[extends父接口名表]{
域类型域名=常量值;//常量域声明
返回值方法名(参数表);//抽象方法声明
}
接口声明中有两个部分是必需的:
interface关键字和接口的名字。
用public修饰的接口是公共接口,可以被所有的类和接口使用;没有public修饰符的接口则只能被同一个包中的其他类和接口利用。
定义接口是为了让某些类实现它。
一个类实现某个接口,要在类的声明中要包括一条implements语句,并提供在接口中定义的所有抽象方法的具体实现。
一个类可以实现多个接口,可以在implements后面列出要实现的多个接口,这些接口以逗号分隔。
publicclassStockAppletextendsAppletimplementsStockWatcher{
publicvoidvalueChanged(StringtickerSymbol,doublenewValue){
if(tickerSymbol.equals(sunTicker))
{……}
elseif(tickerSymbol.equals(oracleTicker))
{……}
}
}
interfaceCollection{
intMAX_NUM=100;
voidadd(Objectobj);
voiddelete(Objectobj);
Objectfind(Objectobj);
intcurrentCount();
}
classFIFOQueueimplementsCollection{
voidadd(Objectobj){
……
}
voiddelete(Objectobj){
……
}
Objectfind(Objectobj){
……
}
intcurrentCount(){
……
}
}
5.5.3接口的实现
实现接口时应注意的问题:
(1)在类的声明部分,用implements关键字声明该类将要实现哪些接口。
(2)类在实现抽象方法时,必须用public修饰符。
(3)除抽象类以外,在类的定义部分必须为接口中所有的抽象方法定义方法体,且方法首部应该与接口中的定义完全一致。
(4)若实现某接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。
但是对于这个抽象类的任何一个非抽象子类,不允许存在未被实现的接口方法。
即非抽象类中不能存在抽象方法。
5.5.4接口的使用
如果接口需要扩充,可以创建新的接口来继承老接口。
比如,可以创建一个StockWatcher的子接口StockTracker:
publicinterfaceStockTrackerextendsStockWatcher{
voidcurrentValue(StringtickerSymbol,doublenewValue);
}
也就是说,接口是共同遵守的协议,一般不要变动,若要变动就创建一个新的接口。
接口除了被继承和实现之外,还可以作为一种类型来使用。
定义了
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 继承
![提示](https://static.bdocx.com/images/bang_tan.gif)