第5章继承和多态.docx
- 文档编号:11237204
- 上传时间:2023-02-26
- 格式:DOCX
- 页数:28
- 大小:170.06KB
第5章继承和多态.docx
《第5章继承和多态.docx》由会员分享,可在线阅读,更多相关《第5章继承和多态.docx(28页珍藏版)》请在冰豆网上搜索。
第5章继承和多态
第5章继承和多态
继承和多态性是面向对象程序设计的重要内容。
继承机制是实现软件构件复用的一种强有力的手段。
多态性是面向对象编程的重要特性,是继承产生的结果。
Java语言很好的体现了继承和多态性两大特性。
本章将讨论用Java语言实现继承和多态性,具体将介绍继承概念、继承的实现、抽象类的作用、方法的覆盖以及用接口实现多继承。
5.1继承
继承是面向对象程序设计的三大概念之一,是面向对象程序的重要概念,它使程序代码复用成为可能。
假设已经定义和实现类DigitalProduction(代表数码产品),需要定义一个新的应用类MobilePhone(代表手机)。
由于手机是数码产品的一种,所以没有必要对类DigitalProduction中属于数码产品特征重新书写代码,只需要继承类DigitalProduction的属性特征即可。
这样,类MobilePhone通过继承获得了类DigitalProduction的数据和方法。
由于类MobilePhone也具有自身的特征,如“进网许可号”,可以为这些异于其他数码产品的特征定义成员数据和成员方法。
这样,MobilePhone继承于DigitalProduction,又具有新的属性特征。
图5-1可以表示了两个类之间的继承关系。
图5-1MobilePhone继承DigitalProduction
从这个意义上来说,继承是指一个新的类继承原有类的基本特性,并增加新的特性。
通俗地说,新的类与原有类之间体现了一种“is-a”关系。
只要类和类之间存在的继承关系,这种扩展可以一直延续下去,它体现出类的层次结构。
例如,而类MobilePhone继承于类DigitalProduction。
而类IntelligentMobile(代表智能手机)继承于类MobilePhone。
继承可以分成两大类型:
单继承和多继承。
单继承是一个类只能从一个类派生而来,即只有一个父类。
多继承是一个类可以从多个类派生而来,可以有多个父类。
Java语言只支持单继承,不支持多继承。
5.1.1父类和子类
Java语言中,继承实际上是一个类扩展一个已有的类。
被扩展的类是父类,而扩展类是子类。
子类继承了父类的类成员,并可以定义自己的独特的属性成员。
通常体现了子类和父类之间是派生与被派生的关系。
所以,有时称父类为基类,而子类称为派生类。
Java语言也体现出类的层次结构。
Java语言中定义类java.lang.Object,它是所有类的父类。
其他类无论是直接还是间接都是继承了Object类,具有Object类的属性,如在第4章中说明的finalize()方法。
比如,Java语言提供的类java.lang.String直接继承于类java.lang.Object,而javax.swing.JOptionPane则是间接继承于java.lang.Object,JOptionPane类具体继承情况见图5-2。
同样,用户自定义类也是直接或间接继承于java.lang.Object类,具体实现见5.1.2。
图5-2javax.swing.JOptionPane的继承示意
5.1.2继承的实现
Java语言中是通过关键字extends来实现单继承的。
简单的实现格式如下:
class子类名extends父类名{
类体
}
有一点要注意,如果在格式中没有通过extends关键字来标明父类名,这并不是意味着该类无父类,相反,它表示该类是java.lang.Object的子类。
例5.1类继承的示例。
publicclassBaseClass{//定义一个类BaseClass
publicintintValue;//定义成员数据intValue;
publicBaseClass(){
intValue=3;
System.out.println("BaseClass定义");
}
publicvoidshowBaseMessage(){
System.out.println("BaseClass信息");
}
}
publicclassDerivedClassextendsBaseClass{//定义类DerivedClass
publicDerivedClass(){
System.out.println("DerivedClass定义");
}
publicvoidshowDerivedMessage(){
System.out.println("继承而来的数据:
"+intValue);//引用继承的成员数据intValue
System.out.println("DerivedClass信息");
}
publicstaticvoidmain(Stringargs[]){
DerivedClassdc=newDerivedClass();
dc.showBaseMessage();//引用继承的成员方法showBaseMessage
dc.showDerivedMessage();
System.exit(0);
}
}
图5-3例5.1的运行结果
例5.1定义了两个类BaseClass和DerivedClass,其中DerivedClass是BaseClass的子类,BaseClass是DerivedClass的父类。
它们之间通过定义DerivedClass中增加extends子句来实现继承关系。
尽管类BaseClass的定义没有用关键字extends来说明父类,但实际上它是隐性的定义为java.lang.Object的子类。
这样,java.lang.Object、BaseClass和DerivedClass之间就构成了继承关系链。
创建一个子类如DerivedClass的对象时,会从上向下调用Object-BaseClass-DerivedClass继承链的默认构造方法。
从图5-3中可以看出,创建一个DerivedClass的对象之前先调用了父类BaseClass的默认构造方法。
注意,如果在继承链中存在某个父类没有定义默认构造方法,会产生编译错误。
不过,尽管子类可以继承父类的成员数据和成员方法,但并不意味着子类可以完全继承父类的全部属性。
如果将例5.1中类BaseClass的公有(public)成员数据intValue改成私有(private)成员数据,再编译类Derived,会发现程序出现编译错误。
这是由于类成员的访问控制限制了类成员的可见性。
父类的私有成员是不能被子类所继承。
就好比孩子继承了父亲的部分特征,而不是全部特征。
5.2抽象类
在现实生活中,可以发现这样的现象:
许多实物抽象出成为一个共同的类别,如“交通工具”,但是“交通工具”并直接对应着实际存在的类别如卡车、自行车、三轮车等。
又如“动物”,并不特指具体的某个实际存在,它只是哺乳动物、两栖动物等以有机物为食物,可运动生物的统称。
“交通工具”、“动物”等这些只是作为一个抽象概念存在,对一组固定实现的抽象描述,并不对应具体的概念。
因此,在面向对象程序设计中,可以将这些抽象描述定义为抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。
由于抽象类对一组具体实现的抽象性描述,所以抽象体是不能修改的。
通常抽象类不具备实际功能,只用来派生子类。
5.2.1抽象方法
Java语言中,用关键字abstract修饰的方法为抽象方法。
语法格式形如:
abstract返回值类型方法名([形式参数]);
例如:
abstractvoidshowMessage();
抽象方法只有方法头没有方法体,即只提供方法的规格说明,没有具体的实现。
抽象方法只能出现在抽象类(见5.2.2)和接口(见5.4)中,其他类中定义会产生编译错误。
有两点必须注意:
1)final关键字不能修饰抽象方法。
这是因为final是不可修改的方法是最终的方法,而抽象方法没有方法体,能被派生类进行覆盖;
2)static是不能修饰抽象方法的。
静态方法占据固定内存空间,而抽象方法根本就不可能运行,不会加载到内存中。
所以,static关键字不能修饰抽象方法。
5.2.2抽象类
抽象类是用关键字abstract修饰的类为抽象类。
语法格式形如:
abstractclass抽象类名{
类体
}
抽象类只是具体实体的抽象描述,是不可以实例化对象。
尽管抽象类定义构造方法是没有语法错误,但是没有实际意义。
抽象类中可以定义非抽象方法,也可以定义抽象方法。
抽象方法在它的派生子类中可以覆盖,来实现具体功能。
抽象类派生出的子类,必须对抽象类中的抽象方法重写。
如果没有对抽象方法重写,该子类须定义为抽象类,否则产生编译错误。
与抽象方法类似,如果一个类要定义为抽象类,就不能用关键字static或关键字final修饰,原因同抽象方法,在这里就不再说明。
例5.2抽象类的示例。
publicabstractclassAbstractClassExample{//AbstractClassExample.java
publicabstractvoidshowMessage();//定义抽象方法showMessage()
publicvoidprintClassName(Stringname){//定义非抽象方法printClassName(String)
System.out.println(name);
}
}
publicclassDerivedAbstractClassextendsAbstractClassExample{//DerivedAbstractClass.java
publicDerivedAbstractClass(){
System.out.println("DerivedAbstractClassDefinition");
}
publicvoidshowMessage(){//覆盖父类的showMessage()方法;
System.out.println("DerivedAbstractClassMessage");
}
publicstaticvoidmain(Stringargs[]){
DerivedAbstractClassdac=newDerivedAbstractClass();
dac.showMessage();
dac.printClassName("Classnameis:
DerivedAbstractClass");//引用继承来的方法
System.exit(0);
}
}
图5-4例5.2的运行结果
5.3多态性
多态性是面向对象程序设计的重要特性,它是继承机制产生的结果。
所以,继承是多态的前提。
面向对象程序设计中,严格的来说多态性是运行绑定机制。
这种机制是实现将方法名绑定到方法具体实现代码。
通俗地理解就是“一个名字,多种形式”。
实际上,最常见的多态的是符号“+”,有如下的表示式:
6+5//实现整数相加
3+5.0f//将3隐性转化为float类型,实现单精度数字的相加
"IAMCHINESE"+4//实现字符串连接操作。
同一个“+”有多种含义:
整数相加、单精度数字相加、双精度数字相加、字符串连接等操作。
当然,上述的表达式3+5.0f中数据类型的隐性的转换也是一种多态的体现。
又如,在第三章讨论的对象的finalize()方法也是多态。
因为,不同类型的对象都有finalize()方法,但根据要求的不同,可以赋予finalize()方法不同代码内容,产生不同的功能。
另外,类的构造方法也是多态的一种形式。
在创建对象时,根据参数的性质来选择构造方法初始化对象。
根据消息选择响应方法的角度来看,多态分成两种形式:
编译多态(Compile-timePolymorphism)和运行多态(Run-timePolymorphism)。
在编译时期根据信息选择响应的方法称为编译多态。
Java语言中,实现编译多态性主要有方法的重载。
运行的多态是在程序运行的时才可以确认响应的方法。
通常运行的多态是通过继承机制引起的。
从实际编程的角度来看,Java语言实现多态性有三种形式:
1)方法的重载;2)通过继承机制而产生的方法覆盖;3)通过接口实现的方法覆盖(见5.4.2)。
在本节中对方法的重载和方法的覆盖做一个详细的介绍。
5.3.1方法的重载
重载(Overloading)实质上就是在一个类内用一个标识符定义不同的方法或符号运算的方法名或符号名。
Java语言中支持符号的重载,如前提及的运算符“+”。
不过Java语言中不支持用户自定义的符号重载,可以支持用户定义方法的重载。
方法的重载具体可以理解为,同一个方法名对应不同的方法定义。
这些方法的格式说明中的参数不同,即,具体涉及到参数的类型、参数的个数是有所不同的。
值得注意的是,在Java语言中,方法的返回值和方法的访问控制不作为区分方法的一个因素。
请看下列的方法定义格式:
publicvoidshowMessage()
publicvoidshowMessage(intx)//与
不同的参数
publicvoidshowMessage(inty)//与
不同的参数名
privatevoidshowMessage()//与
不同的访问控制
publicintshowMessage()//与
不同的返回值类型
在这些表达式中方法定义
和方法定义
是可以视为方法的重载。
方法定义
和方法定义
和方法定义
不是方法的重载。
在编译时会将方法定义
、方法定义
和方法定义
作为同一种方法,如果这三种形式的showMessage()方法放在同一个类中,会产生编译错误。
尽管方法定义
和方法定义
具有不同的参数名,但二者是同一个方法定义,不能视之为重载,因为参数个数和参数类型相同。
例5.3Java中方法重载的示例。
publicclassOverloadingExample{
publicOverloadingExample(){//无参构造方法
System.out.println("方法重载示例:
无参构造方法");
}
publicOverloadingExample(Stringstring){//有参构造方法
System.out.println("方法重载示例:
有参构造方法,参数:
"+string);
}
publicvoidshowMessage(){
System.out.println("方法重载的信息显示");
}
publicvoidshowMessage(Stringstring){
System.out.println("显示字符串:
"+string);
}
publicvoidshowMessage(intintValue){
System.out.println("显示整数:
"+intValue);
}
publicvoidshowMessage(Stringstring,intintValue){
System.out.println("显示字符串:
"+string);
System.out.println("显示整数:
"+intValue);
}
publicstaticvoidmain(Stringargs[]){
OverloadingExampleole1=newOverloadingExample();
OverloadingExampleole2=newOverloadingExample("对象2");
ole1.showMessage
(1);
ole1.showMessage("对象1");
ole2.showMessage();
ole2.showMessage("对象2");
System.exit(0);
}
}
图5-5例5.3的运行结果
例5.3中,构造方法OverloadingExample()是重载,因为它有两种形式:
有参和无参。
在主方法main()中,根据实参情况不同,由编译器分别调用无参的构造方法和有参的构造方法,创建的对象ole1和ole2。
对象ole1和对象ole2调用成员方法showMessage()是重载的,根据调用的实际参数的类型,编译器自动加载不同的showMessage()方法形式。
例如,运行ole1对象的showMessage
(1),编译器将showMessage名绑定到方法showMessage(int)定义的代码中,具体的运行结果观察图5-5。
在下面通过一个实际问题来讨论重载。
例5.4定义一个类Counter,具有实现求绝对值运算的功能。
publicclassCounter{
publicCounter(){
System.out.println("求绝对值");
}
publicintabs(intx){//整数求绝对值
returnx>=0?
x:
-x;
}
publiclongabs(longx){//长整数求绝对值
returnx>=0?
x:
-x;
}
publicfloatabs(floatx){//单精度求绝对值
returnx>=0?
x:
-x;
}
publicdoubleabs(doublex){//双精度求绝对值
returnx>=0?
x:
-x;
}
publicstaticvoidmain(Stringargs[]){
Counterc=newCounter();
System.out.println("-30.445的绝对值="+c.abs(-30.445));
System.out.println("-30的绝对值="+c.abs(-30));
System.exit(0);
}
}
图5-6例5.4的运行结果
5.3.2方法的覆盖和隐藏
方法的覆盖(Overriding)实质是指子类具有重新定义父类成员方法的能力。
这种重新定义表示子类定义的方法具有和父类的方法同名称、同参数类型、同参数个数、以及同返回值。
方法格式定义尽管相同,但具有不同的方法体,实现的内容根据程序员的要求而有所不同。
子类的方法覆盖父类的同名方法。
这意味着,子类对象的实例方法调用只会调用子类中定义的方法,而不是父类中同格式说明的方法。
从第4章介绍可以知道,类的静态方法是一种类方法,对该类的所有对象是共享的。
有一种特殊的情况,就是子类覆盖了父类内定义的静态方法。
这时,父类的静态方法被隐藏起来了,在子类中失去作用。
要求子类定义的方法必须和父类的静态方法具有相同性质和相同的方法格式说明。
即,同为静态类型、同方法名、同方法参数类型、同方法参数个数、同返回值。
如果子类定义的方法在重新定义父类的静态方法时,没有用关键字static定义,则会产生错误。
因为,用一句话可以概括成:
如果子类的方法覆盖父类的静态方法,则父类的方法在子类中隐藏起来了。
为了说明覆盖的定义,请看例5.5。
例5.5方法覆盖和隐藏的示例。
publicclassParentClass{//ParentClass.java
publicstaticStringgetMessage(){//ParentClass类方法getMessage();
return"获取ParentClass类的对象的信息";
}
publicvoidshowMessage(Stringmessage){//被ChildClass的showMessage()方法隐藏
System.out.println("输出ParentClass类的对象的信息:
"+message);
}
}
publicclassChildClassextendsParentClass{//ChildClass.java
publicstaticStringgetMessage(){//定义ChildClass类方法getMessage();
return"获取ChildClass类的对象的信息";
}
publicvoidshowMessage(Stringmessage){//覆盖了ParentClass的showMessage()方法。
System.out.println("输出ChildClass类的对象的信息:
"+message);
}
}
publicclassOverridingTest{//定义测试类OverridingTest
publicstaticvoidmain(Stringargs[]){
ParentClasscc=newChildClass();//对象变量cc引用ChildClass的对象
System.out.println("输出一个对象");
cc.showMessage("WritenbyChen");//调用对象cc的方法showMessage()
System.out.println(cc.getMessage());//输出对象cc的获取信息
System.out.println("输出另外一个对象");
cc=newParentClass();//对象变量cc引用ParentClass类的对象
cc.showMessage("WritenbyChen");
System.out.println(cc.getMessage());
System.out.println("再输出一个ChildClass的对象");
ChildClasscc2=newChildClass();//创建对象cc2
cc2.showMessage("WritenbyChen");
System.out.println(cc2.getMessage());
System.exit(0);
}
}
图5-7图5.5的运行结果
从图5-7中显示的运行结果可以观察到两方面的内容。
首先,子类ChildClass的showMessage()方法覆盖了父类ParentClass的同名方法。
在子类ChildClass中定义getMessage()覆盖了父类的类方法getMessage(),而父类的类方法getMessage()在子类中隐藏,没有发挥作用。
如果在子类定义方法getMesssage()中没有用关键字static修饰,这时会产生编译错误。
其次,在上例中,对象变量cc声明为ParentClass的对象,但是该对象变量即可引用ParentClass的对象,也可以引用ChildClass对象。
具体引用对象的类型,取决于在运行期间该对象变量引用的对象的类型,这就是运行时的多态。
子类覆盖父类的方法,访问控制可以不同。
但是,子类的访问控制的访问权限不能低于父类的同名方法访问权限。
否则,会产生编译错误。
例如,将例5.5中的ParentClass.java和ChildClass.java改成如下形式:
pub
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第5章 继承和多态 继承