1、学习导读 本章讨论面向对象的编程(OOP)及其关键技术:继承和多态、接口、包。 继承性是软件复用的一种形式,对降低软件复杂性行之有效。继承性同时是面向对象程序设计语言的特点,采用对象但没有继承性的语言是基于对象的语言,但不是面向对象的语言,这是两者的区别。 多态性允许以统一的风格处理已存在的变量及相关的类,使增加系统中新功能变得容易。,第5章 继承,教学重点与难点 理解继承和复用的概念 理解父类和子类 掌握扩展类编程 理解多态性是如何扩充和维护系统性能 掌握如何建立包和接口,第5章 继承,第5章 继承,5.1 继承的概念 5.2 扩展类 5.3 多态与动态绑定 5.4 构造函数的继承与重载 5
2、.5 包 5.6 接口 5.7 本章小结 5.8 思考与练习,5. 1 继承的概念,继承性是面向对象程序设计语言的最主要的特点,是其他语言(如面向过程语言)所没有的。,类之间的继承关系是现实世界中遗传关系的直接模拟,它表示类之间的内在联系以及对属性和操作的共享,即子类可以沿用父类(被继承类)的某些特征。当然,子类也可以具有自己独立的属性和操作。,继承,例如,飞行器、汽车和 轮船可归于交通工具类, 飞行器类可以继承交通 工具类某些属性和操作。,除遗传关系外,现实世界中还普遍存在着部分整体关系。例如,飞机可由发动机、机身、机械控制系统、电子控制系统等构成。聚集关系。,5.1.1 继承定义,继承性是
3、软件复用的一种形式。新类由已存在的类生成,通过保留它们的属性和行为,并且根据新类的要求对性能加以修改,添加新的属性和行为。 如果子类只从一个父类继承,则称为单继承;如果子类从一个以上父类继承,则称为多继承。注意 Java不支持多重继承,但它支持“接口”概念。接口使Java获得了多重继承的许多优点,摒弃了相应的缺点。,父类名跟在extends关键字后面,用来说明当前类是哪个已经存在类的子类,存在继承关系。,定义 雇员类 Employee 的两个子类: 一般雇员类:CommonEmployee 主 管 类:ManagerEmployee,子类从父类继承有两个主要的方面: (1)属性的继承。例如,公
4、司是一个父类,一个公司有名称、地址、经理、雇员等,这些都属于结构方面。 (2)方法的继承。一个父类定义了若干操作,如一个公司要有项目、利润、任命经理、录用职工等操作,子公司也将继承这些行为。,5.2.1 继承关系定义,5. 2 扩展类,class CommonEmployee extends Employee 子类1: int m_ManagerNo ;定义类属性m _ManagerNo,代表雇员上司的编号 class ManagerEmployee extends Employee 子类2: int m_SecretaryNo; 定义类属性m_SecretaryNo,代表秘书的编号 ,两个子
5、类,声明类头-父类名,5.2.2 属性继承与隐藏,尽管Employee类是一个父类,但是并不因为它是父类就意味着它有更多的功能。恰恰相反,子类比它们的父类具有更多的功能。因为子类是父类的扩展,增加了父类没有的属性和方法。,(1)子类不能访问父类的private成员,但子类可以访问其父类的public, (2)protected访问是public和private访问之间一个保护性的中间层次。 (3)由于被继承的父类成员没有在子类声明中列出,但是这些成员确实存在于子类中。,5.2.2 属性继承与隐藏,Manger类增添了一个新的字段用于存储奖金,并且增添了一个新方法用于设置它的值: class M
6、anager extends Employee public void setBonus(double b) bonus=b; private double bonus; ,5.2.3 方法继承、覆盖与重载,对于Manager对象,可以使用getName和getHireDay等方法。即使这些方法没有明显地在Manager类中定义,它们也自动地从Employee类中继承过来了。 2方法覆盖 方法的覆盖是指:子类定义同名方法来覆盖父类的方法,是多态技术的一个实现。当父类方法在子类中被覆盖时,通常是子类版本调用父类版本,并做一些附加的工作。,1方法继承,关于覆盖应注意的事项:,(1)不使用super
7、而希望引用父类方法会导致无限的递归,因为子类方法实际上是在调用它自己。 (2)当通过父类引用调用一个方法时,Java会正确地选择与那个对象对应的类的覆盖方法。 (3)方法覆盖中,子类在重新定义父类已有的方法时,应保持与父类完全相同的方法头声明,即与父类完全相同的方法名、返回值和参数列表。 (4)子类可以添加字段,也可以添加方法或者覆盖父类中的方法。然而,继承不能去除父类中的任何字段和方法。,3方法重载,重载的定义:可以用相同的方法名但不同的参数表来定义方法(参数表中参数的数量、类型或次序有差异),这称为方法重载。,重载(overloading):当多个方法具有相同的名字而含有不同的参数时,便发
8、生重载。编译器必须挑选处调用哪个方法。它通过将在不同方法头部中的参数类型和在特定的方法调用中使用值的类型进行比较,从而挑选出正确的方法。,5.2.4 在子类中使用构造函数,关于子类构造函数的规律总结如下: (1)子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子类的父类成员。 (2)构造函数不能继承,它们只属于定义它们的类。如果把一个对象指定为其父类的引用,那么允许把这个对象转换回它自己原来的类型,为了向该对象传送一些在父类中没有的信息,必须这样做。 (3)当创建一个子类对象时,子类构造函数首先调用父类的构造函数并执行,接着才执行子类构造函数。,5.2.5 父类对象与子
9、类对象的关系,调用过程:e. getSalary() 程序会选择正确的getSalary方法。注意尽管e的声明类型是Employee. 当e指向一个Employee对象时,e.getSalary()会调用Employee类中的getSalary方法;而当e指向一个Manager对象时,getSalary()方法就变成了Manager类的getSalary()方法。虚拟机知道e所指对象的实际类型,因此它会调用正确的方法。 事实上,一个对象变量(如e)可以指向多种实际类型这种现象称为“多态”。在运行时自动选择正确的方法进行调用称作动态绑定。,5.2.6 扩展类继承的应用示范,现在讨论一个继承性的具
10、体例子5-2,点、圆的层次结构。首先建立并使用Point类,然后从point类继承产生Circle类。 程序的模块: (1)模块1:程序定义了一个Point类; (2)模块2:又定义了Circle类,它是从Point类继承来的; (3)应用程序,该程序演示了如何把子类引用指定为父类引用,以及把父类引用强制转换为子类引用,注意当p添加到string时对toString的隐式调用。,用来完成这个效果的两项关键编程技术是: (1)创建Circle类继承了Point类。 (2)在Point类和Circle类中用完全相同的signature覆盖toString方法。 值得强调的是以下几点: (1)Jav
11、a将隐式地把Object类(java.lang包)作为新定义类的父类。Object类提供了一套任何类的任何对象均可使用的方法。 (2)子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子类的父类成员;构造函数不能继承,它们只属于定义它们的类。(3)父类的实例变量可用Protected限定,因此,从Point衍生Circle类时,Circle类的方法能直接引用坐标x和y,而不必使用访问。,5.3 多态与动态绑定,多态(Polymorphism)提高了程序可扩充性,调用多态性行为的软件传送给对象的消息(即方法调用)与对象的类型无关,因此能响应已有消息的新类型可以直接加入系统,
12、而不用修改基本系统。 在运行时自动选择正确的方法进行调用称作动态绑定(dynamic binding)。,5.3.1 多态,对于数据来说,继承是否为正确的设计可以用一个简单的规则来判断。“is-a”规则表明子类的每一个对象都是一个超类的对象。例如,每一个经理是一个员工。然而,只有经理类是员工类的子类才是有意义的。很明显,反过来就不行了并不是每个员工都是经理。还有一个明确叙述“is-a”规则的方法是替代原则。该原则规定无论何时,如果程序需要一个超类对象,都可以用一个子类对象来代替。,5.3.2 动态绑定,理解调用一个对象方法的机制是非常重要的。下面具体介绍: (1)编译器检查对象的声明类型和方法
13、名。 (2)接着,编译器检查方法调用中的参数类型。如果在所有的叫做f的方法中有一个其参数类型同调用提供的参数类型最匹配,那么该方法就会被选择调用。这个过程称作超载选择。 (3)当程序运行并且使用动态绑定来调用一个方法时,那么虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。,动态绑定是非常重要的特性:它能使程序变得可扩展而无需重编译已存代码。,5.3.3父类对象与子类对象的使用与转化,this表示的是当前对象本身,更准确地说,this代表了当前对象的一个引用。对象的引用可以理解为对象的另一个名字,通过引用可以顺利地访问对象,包括修改对象的属性、调用对象的方法。,1this,现介绍程序里
14、父类对象与子类对象间的指代使用和转化。this和super是常用来指代父类对象与子类对象的关键字.,super表示的是当前对象的直接父类对象,是当前对象的直接父类对象的引用。所谓直接父类是相对于当前对象的其他“祖先”而言的。,2super,提供一个构造函数: public Manager(String n,double s,int year,int month,int day) super(n,s,year,month,day); bonus=0; 这里关键字super具有不同的意义。代码: super(n,s,year,month,day); 的意思是“调用Employee父类的构造函数,同
15、时带有n、s、year、month以及day参数”。,5.3.3父类对象与子类对象的使用与转化,父类对象与和子类对象的转化需要注意如下的原则: (1)子类对象可以被视为是其父类的一个对象; (2)父类对象不能当成是其某一个子类的对象; (3)如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为形式参数; (4)如果父类对象引用指向的实际是一个子类对象,那么这个父类对象的引用可以用强制类型转换转化成子类对象的引用。,5.3.4 多态性在工资系统中的应用,下面给出了一个根据员工类型利用抽象方法和多态性完成工资单计算的程序。Employee是抽象(abstract)父类,
16、Employee的子类有经理Boss,每星期发给他固定工资,而不计工作时间;普通雇员CommissionWorker,除基本工资外还根据销售额发放浮动工资;对计件工人PieceWorker,按其生产的产品数发放工资;对计时工人HourlyWorker,根据工作时间长短发放工资。该例的Employee的每个子类都声明为final,因为不需要再由它们生成子类。,对程序的几点说明: (1)对所有员工类型都使用earnings方法,但是每个人挣的工资按他所属的员工类计算,所有员工的类都是从父类Employee继承出的。 (2)如果一个子类是从一个具有abstract方法的父类继承的,子类也是一个abs
17、tract类并且必须被显式声明为abstract类。 (3)一个abstract类可以有实例数据和非abstract方法,而且它们遵循一般的子类继承规则。 (4)现在分析一下Employee类,其中public方法包括:构造函数,以及一个abstract方法earnings。为什么earnings方法应是abstract呢?因为在Employee类中为这个方法提供实现是没有意义的,谁也不能为一个抽象的员工发工资,而必须先知道是哪种员工。 (5)Boss类是从Employee中继承出来的,其中public方法包括一个以名、姓和每周工资作为参数的构造函数 (6)CommissionWorker类从
18、Employee中继承出来。 (7)PieceWorker类,也是从Employee继承。 (8)HourlyWorker类亦从Employee继承。 (9)Test应用程序的main方法首先声明了ref为Employee引用。,5.4 构造函数的继承与重载,5.4.1 默认字段初始化,构造函数的作用是来定义对象的初始状态。然而由于对象的构造如此重要,Java还另外提供了一些不同的机制来编写构造函数。,如果在构造函数中没有明确地给某个字段赋值,那么此字段会被自动地赋值以一个默认值:若是数字则被赋值以0,若是布尔类型则被赋值以false,若是对象引用则被赋值以null。但使用默认值被认为是一种糟
19、糕的编程做法。因为,如果字段以不可见的形式被初始化会使得别人很难读懂程序。,5.4.2 默认构造函数,默认构造函数是指没有参数的构造函数(这种构造函数有时也称作无参数构造函数)。例如,下面是个Employee类的默认构造函数: public Employee() name=”; salary=0; hireDay=new Date(); ,默认构造函数是指没有参数的构造函数(这种构造函数有时也称作无参数构造函数)。例如,下面是个Employee类的默认构造函数: public Employee() name=”; salary=0; hireDay=new Date(); 如果编写了一个没有构
20、造函数的方法,则系统会自动为此方法提供一个默认构造函数。此默认构造函数将所有的实例字段初始化为默认值。,5.4.3 显式字段初始化,由于在类中可以重载构造函数方法,所以可以采用多种方式来设置类中实例字段的初始状态不管什么样的构造函数调用,确保每个实例字段都被设置为某个有意义的值是一种很好的习惯。在类的定义中,可以简单地把一个值赋值给任何字段。例如, private String name=”lili”在执行构造函数前,此赋值会被执行。当类中所有的构造函数都需要把某一特定的实例字段赋值以相同的值时,此语法非常有用。,5.4.4 参数名,当编写小的构造函数时,一般选择单个字母作为参数名。 publ
21、ic Employee(String n,double s) name=n; salary=s; 然而,这么做的缺点是需要阅读代码才能知道参数n和s表示的是什么。 一些程序员在每个参数前加一前缀“a”:,5.4.5 调用另一个构造函数,关键字this指向隐式参数。此外,此关键字还有另一种含义。如果构造函数第一个语句具有形式this(),那么此构造函数调用此类中的另一个构造函数。下面是一个典型的实例: public Employee(double s) /calls Employee(String,double) this(“Employee #”+nextId,s); nextId+; ,5.
22、4.6 初始化块,在Java中实际上还有第三种机制,它叫做初始化块。在类声明中可以包含任意数量的代码块。只要构造了此类的一个对象,这些代码块就会被执行。例如: class Employee public Employee(String n,double s) name=n; salary=s; ,5.4.6 初始化块,public Employee() name=” ”; salary=0; /对象初始化模块 id=nextId; nextId+; ,5.4.6 初始化块,private String name; private double salary; private int id; p
23、rivate static int nextId; 在这个例子中,id字段在对象初始化块中被初始化,而不管是哪个构造函数构造了一个对象。初始化块首先被运行,然后构造函数的主体部分被执行。,5.5 包,5.5.1 包用途,Java允许把多个类收集在一起成为一组,称作包(package)。包便于组织任务,以及使自己的任务和其他人提供的代码库相分离。标准Java库被分类成许多的包,其中包括java.1ang、java.util和等等。标准Java包是分层次的。就像在硬盘上嵌套有各级子目录一样,可以通过层次嵌套组织包。所有的Java包都在Java和Javax包层次内。,5.5.2 创建包,已经看到,已
24、有的库,比如Java API中的类和接口,可以导入到Java程序中。 Java API中的每一个类和接口属于一个特定的包。它包含一组相关联的类和接口,实际是对类和接口进行组织的目录结构。 例如,假定文件名是MyClass.java。它意味着在那个文件有一个、而且只能有一个public类。而且那个类的名字必须是MyClass(包括大小写形式): package mypackage; public class MyClass ,5.5.2 创建包,创建可复用的类的步骤简要说明如下: (1)定义一个public类。如果类不是public,它只能被同一包中的其他类使用。 (2)选择一个包名,并把pac
25、kage语句加到可复用的类的源代码文件中。 (3)编译这个类。这样,它就被放到适当的包目录结构中,以供编译器和解译器使用。 (4)把这个可复用的类导入到需要用它的程序中。现在就可以使用它了。 注意 在Java语言中可以出现在类定义的括号外面的仅有两个语句,它们是package和import。 ,5.5.3 包引用-每个类名前加上完整的包名,例如,给出一个指向此包中的类的快捷方式。一旦使用import(导入)了以后,就不再需要给出完整的包名。 可以引入一个特定的类,也可以引入整个包。import语句要放在源文件的头部(但在所有package语句的下面)。例如,可以通过下面的语句引入在java.u
26、til包中的所有的类: import java.util.*; 然后,就可以使用 Date today=new Date(); 而不需要在前面加上包名。也可以引入包中某个特定的类: import java.util.Date;,5.5.3 包引用-向包中添加类,要把类放人一个包中,必须把此包的名字放在源文件头部,并且放在对包中的类进行定义的代码之前。例如,在文件Employee.java的开始部分如下: package com.horstmann.corejava; public class Employee 把包中的文件放入与此完整的包名相匹配的子目录中。例如,在包com.horstmann
27、.corejava中的所有的类文件都必须放在子目录com/horstmann/core.java(Windows下的comhorstmanncorejava)下。这是最简单的一种方法,在本章的后面可以看到更多的选项。,5.5.3 包引用-定位类,类被存储在文件系统的子目录中。类的路径必须与所在包名相匹配。 在前面的例子中,包目录com/horstmann/corejava是程序目录的一个子目录。然而这样安排很不灵活。一般,有多个程序需要访问包文件。为了使包可以在多个程序间共享,需要做以下事情: 1)把类放在一个或多个特定的目录中,比如/home/user/classdir。此目录是包树的基本目
28、录。如果加入了类com.horstmann.corejava.Employee,那么此类文件必须位于子目录/home/user/classdir/com/horstmann/corejava下。 2)设置类路径。类路径是其子目录包含类文件的所有基本目录的集合。,5.5.3 包引用-标记包作用域,已经接触过public和private访问指示符。 被标记为Public的部件可以被任何类使用,而私有部件只能被定义它们的类使用。如果没有指定public或private,那么部件(即类、方法或变量)可以被同一个包中的所有方法访问。,5.5.4 Java API包,为了简化面向对象的编程过程,Java系
29、统事先设计并实现了一些体现了常用功能的标准类,如用于输入输出的类,用于数学运算的类,用于图形用户界面设计的类,用于网络处理的类等。这些系统标准类根据实现的功能不同,可以划分成不同的集合,每个集合是一个包,合称为类库。可以引用这些包,也可以创建自己的包。Java的类库是系统提供的已实现的标准类的集合,是Java编程的API,它可以帮助开发者方便、快捷地开发Java程序。 表5-1 Java API基本包一览,5.6 接口,5.6.1 接口概念接口主要作用是可以帮助实现类似于类的多重继承的功能。在Java中,出于简化程序结构的考虑,不再支持类间的多重继承而只支持单重继承,即一个类至多只能有一个直接
30、父类。然而在解决实际问题的过程中,仅仅依靠单重继承在很多情况下都不能将问题的复杂性表述完整,需要其他的机制作为辅助。,在Java语言中,接口(Interface)是对符合接口需求的类的一套规范。接口与包相似,也是用来组织应用中的各类并调节它们的相互关系的一种结构,更准确地说,接口是用来实现类间多重继承功能的结构。,5.6.2 接口声明,Java中声明接口的语法如下: public interface 接口名 extends 父接口名列表 /接口体; /常量域声明public static final 域类型 域名=常量值;/抽象方法声明public abstract native返回值 方法名
31、(参数列表) throw异常列表; 从上面的语法规定可以看出,定义接口与定义类非常相似,实际上完全可以把接口理解成为一种特殊的类,接口是由常量和抽象方法组成的特殊类。,5.6.2 接口声明,(1)接口中的属性都是用 final修饰的常量,在这个类中,所有的成员函数都是抽象的,也就是说它们都只有说明没有定义; (2)接口中的方法都是用abstract修饰的抽象方法,在接口中只能给出这些抽象方法的方法名、返回值和参数列表,而不能定义方法体,即仅仅规定了一组信息交换、传输和处理的“接口”。,5.6.2 接口的实现,一个类要实现某个或某几个接口时,有如下的步骤和注意事项: (1)在类的声明部分,用im
32、plements关键字声明该类将要实现哪些接口; (2)如果实现某接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法,即为所有抽象方法定义方法体,而且方法头部分应该与接口中的定义完全一致,即有完全相同的返回值和参数列表; (3)如果实现某接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。 (4)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头。(5)接口的抽象方法,其访问限制符都已指定是public,所以类在实现方法时,必须显式地使用public修饰符。,5.7 本章小结,面向对象编程的优点之一是可通过继承性实现软件复用。新类通过继承
33、性可继承父类已定义过的实例变量和方法。在这种情况下,新类叫做子类。单一继承是指一个子类继承一个父类。 多重继承是指一个子类继承多个父类。Java不支持多重继承,但Java提供了接口。 接口具有多继承的许多优点,而却没有它的缺点。 在子类中通常要加入它自己的实例变量和方法,所以子类一般要比它的父类大。另一方面,子类比父类更具体,因而代表了较少的对象。,5.7 本章小结,子类不能访问父类的private成员,但子类可以访问其父类的public,protected和包访问成员;要访问父类的包访问成员,子类一定要在父类的包内。 子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子
34、类的父类成员。 继承性实现了软件的复用,这不但节省了开发时间,也鼓励人们使用已经验证无误和调试过的高质量软件。 子类的对象可以当作其父类的对象对待,反之则不行。,5.7 本章小结,父类和子类的关系是一种层次结构关系。当一个类使用继承性机制时,它可以是为其他类提供属性和行为的父类,也可以是继承那些属性和行为的子类。 继承性层次结构的层次数只受系统硬件结构的限制,但大多数层次结构只有几层。层次结构对于理解和管理复杂软件非常有用。 随着软件复杂性的增加,Java提供了通过继承性和多态性支持层次结构的机制。 protected访问是public和private访问之间一个保护性的中间层次。父类方法、子
35、类方法和在同一个包内类的方法都能访问父类的protected成员,但其他方法均不能访问。,5.7 本章小结,父类可以是一个子类的直接父类或间接父类。直接父类是子类显式使用extends说明的类,间接父类是子类从层次结构树的前几层上继承的类。当父类某成员不适合一个子类时,可以在子类中覆盖它。 一个子类对象引用可以隐式地转换成一个父类对象引用。使用显式的类型转换,可以把父类引用转换成子类引用。如果目标不是子类对象,将产生Class CastException例外处理。 父类代表共性,从一个父类派生的所有类都继承了这个父类的功能。,5.7 本章小结,在面向对象设计过程中,设计者力图发现并提取共性以构
36、造父类。子类继承了父类的功能,并根据具体需要来添加功能体需要来添加功能。 由于被继承的父类成员没有在子类声明中列出,所以读子类声明时会感到迷惑,但是这些成员确实存在于子类中。多态性使设计和实现扩充性更强的系统成为可能,当程序已开发出来后,仍旧可以方便的编程处理那些新添加的对象类型。 多态性编程可减少使用switch语句,从而避免了与switch语句相关的错误。,5.7 本章小结,当通过父类引用调用一个方法时,Java会正确地选择与那个对象对应的类的覆盖方法。在多态性中,一个方法调用可能会产生不同动作,这取决于接受调用的对象类型。 当从具体类(子类)中实例化某些对象时,这些引用可以用来对这些对象
37、进行多态性操作。经常要把一些新类加入系统,这时可以用动态方法绑定(也称迟绑定)接纳它们。在编译方法调用时,不需要知道对象的类型。只有执行时,才会选择相应对象的方法。 在动态方法绑定中,执行方法调用时系统将会找到接受调用对象所属的类的相应方法。对于父类提供的方法,子类可以覆盖它的父类版本。,5.7 本章小结,一个接口的定义由关键字interface开始以包含pubic final static数据。但这不是必须的,即子类也可以使用一个方并包含一套public abstract方法,接口也可为使用接口,一个类必须声明实现(关键字implements)接口,指定的参数个数和返回类型定义每个方法。当没有缺省的实现用来继承时,通常使用接口而不使用抽象类。 当某个类实现一个接口时,也有同样的“是一个”继承性关系。并且必须根据接口中为实现一个以上的接口,只要在类定义中的关键字implements后面列出接口名逗号分隔。,