《面对对象的程序设计二》由会员分享,可在线阅读,更多相关《面对对象的程序设计二(46页珍藏版)》请在金锄头文库上搜索。
1、第第4章章 Java面对对象的程序设计(二)面对对象的程序设计(二)学习导读学习导读n本章讨论面向对象的编程(OOP)及其关键技术:继承和多态、接口、包。n继承性是软件复用的一种形式,对降低软件复杂性行之有效。继承性同时是面向对象程序设计语言的特点,采用对象但没有继承性的语言是基于对象的语言,但不是面向对象的语言,这是两者的区别。n多态性允许以统一的风格处理已存在的变量及相关的类,使增加系统中新功能变得容易。5.1 继承的概念继承性是面向对象程序设计语言的最主要的特继承性是面向对象程序设计语言的最主要的特点,是其他语言(如面向过程语言)所没有的。点,是其他语言(如面向过程语言)所没有的。 类类
2、之之间间的的继继承承关关系系是是现现实实世世界界中中遗遗传传关关系系的的直直接接模模拟拟,它它表表示示类类之之间间的的内内在在联联系系以以及及对对属属性性和和操操作作的的共共享享,即即子子类类可可以以沿沿用用父父类类(被被继继承承类类)的的某某些些特特征征。当当然然,子子类类也也可可以以具具有有自自己己独立的属性和操作。独立的属性和操作。继承5.1 继承的概念n继承定义继承定义 继承性是软件复用的一种形式。新类由已存在的类生成,通过保留它们的属性和行为,并且根据新类的要求对性能加以修改,添加新的属性和行为。如果子类只从一个父类继承,则称为单继承;如果子类从一个以上父类继承,则称为多继承。注意
3、Java不支持多重继承,但它支持“接口”概念。接口使Java获得了多重继承的许多优点,摒弃了相应的缺点。5.2 扩展类n1 1 继承关系定义继承关系定义 父类名跟在父类名跟在extends 关键字后面,用来说明当前类是关键字后面,用来说明当前类是哪个已经存在类的子类,存在继哪个已经存在类的子类,存在继承关系承关系。 定义定义 雇员类雇员类 Employee 的两个子类:的两个子类:一般雇员类一般雇员类:CommonEmployee 主主 管管 类类:ManagerEmployee子子类类从从父父类类继继承承有有两个主要的方面:两个主要的方面:(1)属属性性的的继继承承。例例如如,公公司司是是一
4、一个个父父类类,一一个个公公司司有有名名称称、地地址址、经经理理、雇雇员员等等,这这些些都都属属于结构方面。于结构方面。(2)方方法法的的继继承承。一一个个父父类类定定义义了了若若干干操操作作,如如一一个个公公司司要要有有项项目目、利利润润、任任命命经经理理、录录用用职职工工等等操操作作,子子公公司司也将继承这些行为。也将继承这些行为。5.2 扩展类class CommonEmployee extends Employee 子类子类1: int m_ManagerNo ;定义类属性定义类属性m _ManagerNo,代表雇员上司的编代表雇员上司的编号号class ManagerEmployee
5、 extends Employee 子类子类2: int m_SecretaryNo; 定义类属性定义类属性m_SecretaryNo,代表秘书的编代表秘书的编号号声明类头声明类头-父类名父类名两个子类两个子类5.2 扩展类n2 2 属性继承与隐藏属性继承与隐藏n尽管Employee类是一个父类,但是并不因为它是父类就意味着它有更多的功能。恰恰相反,子类比它们的父类具有更多的功能。因为子类是父类的扩展,增加了父类没有的属性和方法(1)子类不能访问父类的private成员,但子类可以访问其父类的public,(2)protected访问是public和private访问之间一个保护性的中间层次。
6、(3)由于被继承的父类成员没有在子类声明中列出,但是这些成员确实存在于子类中。5.2 扩展类n3 方法继承、覆盖与重载方法继承、覆盖与重载1)1)方法继承方法继承对于子类对象,可以使用父类中的方法。即使这些方法没有明显地在子类中定义,它们也自动地从父类中继承过来了。2)2)方法覆盖方法覆盖方法的覆盖是指:子类定义同名方法来覆盖父类的方法,是多态技术的一个实现。当父类方法在子类中被覆盖时,通常是子类版本调用父类版本,并做一些附加的工作。n见例5.1 5.2 扩展类n关于覆盖应注意的事项关于覆盖应注意的事项1、方法覆盖中,子类在重新定义父类已有的方、方法覆盖中,子类在重新定义父类已有的方法时,应保
7、持与父类完全相同的方法头声明,法时,应保持与父类完全相同的方法头声明,即与父类完全相同的方法名、返回值和参数列即与父类完全相同的方法名、返回值和参数列表。表。2、子类可以添加字段,也可以添加方法或者覆、子类可以添加字段,也可以添加方法或者覆盖父类中的方法。然而,继承不能去除父类中盖父类中的方法。然而,继承不能去除父类中的任何字段和方法。的任何字段和方法。3、注意方法覆盖中的、注意方法覆盖中的this和和super见下页:见下页:5.2 扩展类nthis和和super1、this表示的是当前对象本身,表示的是当前对象本身,this代表当前对代表当前对象的一个引用。可以理解为对象的另一个名字。象的
8、一个引用。可以理解为对象的另一个名字。利用利用this可以调用当前对象的方法和属性。可以调用当前对象的方法和属性。如:如:this.getName()()和和getName()()在类在类中是一样的。中是一样的。见例见例1this的使用的使用2、super表示的是当前对象的直接父类对象,表示的是当前对象的直接父类对象,是当前对象的父类对象的引用。是当前对象的父类对象的引用。见例见例2 super的的使用使用5.2 扩展类3 3方法重载方法重载n重载的定义:可以用相同的方法名但不同的参数表来定义方法(参数表中参数的数量、类型或次序有差异),这称为方法重载。重载(overloading):当多个方
9、法具有相同的名字而含有不同的参数时,便发生重载。编译器必须挑选处调用哪个方法。它通过将在不同方法头部中的参数类型和在特定的方法调用中使用值的类型进行比较,从而挑选出正确的方法。 5.2 扩展类n4 4 在子类中使用构造函数在子类中使用构造函数关于子类构造函数的规律总结如下:(1)子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子类的父类成员。(2)构造函数不能继承,它们只属于定义它们的类。(3)当创建一个子类对象时,子类构造函数首先调用父类的构造函数并执行,接着才执行子类构造函数5.2 扩展类n5 5 父类对象与子类对象的关系父类对象与子类对象的关系1 1、子类对象可以
10、被视为是其父类的一个对象;、子类对象可以被视为是其父类的一个对象;2 2、父类对象不可被当作某个子类的对象;父类对象不可被当作某个子类的对象;3 3、如如果果一一个个方方法法的的形形式式参参数数定定义义的的是是父父类类对对象象,那那么么调调用用这这个个方方法法时时可可以以使使用用子子类类对对象象作作为为实实际参数际参数4 4、如如果果父父类类对对象象引引用用指指向向的的实实际际是是一一个个子子类类对对象象,那那么么父父类类对对象象的的引引用用可可以以强强制制类类型型转转换换为为子类对象的引用子类对象的引用例:例:Employee eEmployee eEmployeeManager5.2 扩展
11、类n5 5 父类对象与子类对象的关系父类对象与子类对象的关系调用过程:e. getSalary()程序会选择正确的getSalary方法。注意尽管e的声明类型是Employee.当e指向一个Employee对象时,e.getSalary()会调用Employee类中的getSalary方法;而当e指向一个Manager对象时,getSalary()方法就变成了Manager类的getSalary()方法。虚拟机知道e所指对象的实际类型,因此它会调用正确的方法。事实上,一个对象变量(如e)可以指向多种实际类型这种现象称为“多态”。在运行时自动选择正确的方法进行调用称作动态绑定。5.2 扩展类n6
12、 6 扩展类继承的应用示范扩展类继承的应用示范现在讨论一个继承性的具体例子,点、圆的层次结构。首先建立并使用Point类,然后从point类继承产生Circle类。见例见例3程序的模块:(1)模块1:程序定义了一个Point类; (2)模块2:又定义了Circle类,它是从Point类继承来的;(3)应用程序,该程序演示了如何把子类引用指定为父类引用,以及把父类引用强制转换为子类引用,注意当p添加到string时对toString的隐式调用5.2 扩展类用来完成这个效果的两项关键编程技术是:(1)创建Circle类继承了Point类。(2)在Point类和Circle类中用完全相同的覆盖toS
13、tring方法。值得强调的是以下几点:(1)Java将隐式地把Object类(java.lang包)作为新定义类的父类。Object类提供了一套任何类的任何对象均可使用的方法。(2)子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子类的父类成员;构造函数不能继承,它们只属于定义它们的类。(3)父类的实例变量可用Protected限定,因此,从Point衍生Circle类时,Circle类的方法能直接引用坐标x和y,而不必使用访问。5.3 多态与动态绑定多态(Polymorphism)提高了程序可扩充性,调用多态性行为的软件传送给对象的消息(即方法调用)与对象的类型无关,
14、因此能响应已有消息的新类型可以直接加入系统,而不用修改基本系统。在运行时自动选择正确的方法进行调用称作动态绑定(dynamic binding)。在编译时可以选择正确的方法进行调用称作静态绑定(static binding)5.3 多态与动态绑定n1 1 多态多态对于数据来说,继承是否为正确的设计可以用一个简单的规则来判断。“is-a”规则表明子类的每一个对象都是一个超类的对象。例如,每一个经理是一个员工。然而,只有经理类是员工类的子类才是有意义的。很明显,反过来就不行了并不是每个员工都是经理。 还有一个明确叙述“is-a”规则的方法是替代原则。该原则规定无论何时,如果程序需要一个超类对象,都
15、可以用一个子类对象来代替5.3 多态与动态绑定n2 2 动态绑定动态绑定理解调用一个对象方法的机制是非常重要的。下面具体介绍:X.f;(1)编译器检查对象的声明类型和方法名。(2)接着,编译器检查方法调用中的参数类型。如果在所有的叫做f的方法中有一个其参数类型同调用提供的参数类型最匹配,那么该方法就会被选择调用。这个过程称作超载选择。(静态)(3)当程序运行并且使用动态绑定来调用一个方法时,那么虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。动态绑定是非常重要的特性:它能使程序变得可扩展而无需重编译已存代码。5.3 多态与动态绑定n例例n见书P84例5。4和例5。55.4 构造函数的
16、继承与重载n构造函数的作用是来定义对象的初始状态。然而由于对象的构造如此重要,Java还另外提供了一些不同的机制来编写构造函数1 1默认属性初始化默认属性初始化2如果在构造函数中没有明确地给某个属性赋值,那么此字段会被自动地赋值以一个默认值:若是数字则被赋值以0,若是布尔类型则被赋值以false,若是对象引用则被赋值以null。但使用默认值被认为是一种糟糕的编程做法。因为,如果字段以不可见的形式被初始化会使得别人很难读懂程序。5.4 构造函数的继承与重载2 2默认构造函数默认构造函数默认构造函数是指没有参数的构造函数(这种构造函数有时也称作无参数构造函数)。例如,下面是个Employee类的默
17、认构造函数:public Employee()name=”;salary=0;hireDay=new Date();如果编写了一个没有构造函数的类,则系统会自动为此类提供一个默认构造函数。此默认构造函数将所有的实例字段初始化为默认值。 5.4 构造函数的继承与重载n3 3 显式属性初始化显式属性初始化由于在类中可以重载构造函数方法,所以可以采用多种方式来设置类中实例属性的初始状态不管什么样的构造函数调用,确保每个实例字段都被设置为某个有意义的值是一种很好的习惯。 在类的定义中,可以简单地把一个值赋值给任何字段。例如,private String name=”lili” 在执行构造函数前,此赋值
18、会被执行。当类中所有的构造函数都需要把某一特定的实例字段赋值以相同的值时,此语法非常有用5.4 构造函数的继承与重载4 4参数名参数名当编写小的构造函数时,一般选择单个字母作为参数名。public Employee(String n,double s) name=n; salary=s;然而,这么做的缺点是需要阅读代码才能知道参数n和s表示的是什么。5.4 构造函数的继承与重载n5 5 构造函数重载构造函数重载在一个类中可以定义多个构造函数以满足不同的对象实例的初始化情况,这些构造函数通过不同的参数列表来区分。Public Employee(String n)public Employee(S
19、tring n,double s)Public Employee(String n,double s,int ayear,int amonth,int aday)5.4 构造函数的继承与重载n6 6 调用另一个构造函数调用另一个构造函数关键字this指向隐式参数。此外,此关键字还有另一种含义。如果构造函数第一个语句具有形式this(),那么此构造函数调用此类中的另一个构造函数。下面是一个典型的实例:Public Employee(String,double)public Employee(double s)/calls Employee(String,double)this(“Employee
20、 #”+nextId,s);nextId+;5.4 构造函数的继承与重载7 7调用父类的构造函数调用父类的构造函数关键字super指向当前类的直接父类对象。此外,此关键字还有另一种含义。如果构造函数第一个语句具有形式this(),那么此构造函数调用此类的直接父类中的另一个构造函数。:Public Employee(String,double)/处于类Employee中public Manager(double s)/处于类Manager中,为Employee的子类/calls Employee(String,double)in calss Employeesuper(“Employee #”+
21、nextId,s);nextId+;5.5 包n1 1 包用途包用途Java允许把多个类收集在一起成为一组,称作包(package)。包便于组织任务,以及使自己的任务和其他人提供的代码库相分离。 标准Java库被分类成许多的包,其中包括、java.util和等等。标准Java包是分层次的。就像在硬盘上嵌套有各级子目录一样,可以通过层次嵌套组织包。所有的Java包都在Java和Javax包层次内5.5 包n2 2 创建包创建包已经看到,已有的库,比如Java API中的类和接口,可以导入到Java程序中。Java API中的每一个类和接口属于一个特定的包。它包含一组相关联的类和接口,实际是对类和
22、接口进行组织的目录结构。例如,假定文件名是MyClass.java。它意味着在那个文件有一个、而且只能有一个public类。而且那个类的名字必须是MyClass(包括大小写形式):package mypackage;public class MyClass 5.5 包创建可复用的类的步骤简要说明如下:(1)定义一个public类。如果类不是public,它只能被同一包中的其他类使用。(2)选择一个包名,并把package语句加到可复用的类的源代码文件中。(3)编译这个类。这样,它就被放到适当的包目录结构中,以供编译器和解译器使用。(4)把这个可复用的类导入到需要用它的程序中。现在就可以使用它了
23、。注意 在Java语言中可以出现在类定义的括号外面的仅有两个语句,它们是package和import。5.5 包3 3包引用包引用-每个类名前加上完整的包名每个类名前加上完整的包名例如,给出一个指向此包中的类的快捷方式。一旦使用import(导入)了以后,就不再需要给出完整的包名。可以引入一个特定的类,也可以引入整个包。import语句要放在源文件的头部(但在所有package语句的下面)。例如,可以通过下面的语句引入在java.util包中的所有的类:import java.util.*;然后,就可以使用Date today=new Date();而不需要在前面加上包名。也可以引入包中某个特
24、定的类:import java.util.Date;5.5 包要把类放人一个包中,必须把此包的名字放在源文件头部,并且放在对包中的类进行定义的代码之前。例如,在文件的开始部分如下:package com.horstmann.corejava;public class Employee把包中的文件放入与此完整的包名相匹配的子目录中。例如,在包com.horstmann.corejava中的所有的类文件都必须放在子目录com/horstmann(Windows下的comhorstmanncorejava)下。这是最简单的一种方法,在本章的后面可以看到更多的选项。5.5 包类被存储在文件系统的子目录
25、中。类的路径必须与所在包名相匹配。在前面的例子中,包目录com/horstmann/corejava是程序目录的一个子目录。然而这样安排很不灵活。一般,有多个程序需要访问包文件。为了使包可以在多个程序间共享,需要做以下事情:1) 把 类 放 在 一 个 或 多 个 特 定 的 目 录 中 , 比 如/home/user/classdir。此目录是包树的基本目录。如果加入了类com.horstmann.corejava.Employee,那么此类文件必须位于子目录/home/user/classdir/com/horstmann/corejava下。2)设置类路径。类路径是其子目录包含类文件的所
26、有基本目录的集合。 classpath5.5 包已经接触过public和private访问指示符。被标记为Public的部件可以被任何类使用,而私有部件只能被定义它们的类使用。如果没有指定public或private,那么部件(即类、方法或变量)可以被同一个包中的所有方法访问。5.5 包n4 4 Java APIJava API包包为了简化面向对象的编程过程,Java系统事先设计并实现了一些体现了常用功能的标准类,如用于输入输出的类,用于数学运算的类,用于图形用户界面设计的类,用于网络处理的类等。这些系统标准类根据实现的功能不同,可以划分成不同的集合,每个集合是一个包,合称为类库。可以引用这些
27、包,也可以创建自己的包。 Java的类库是系统提供的已实现的标准类的集合,是Java编程的API,它可以帮助开发者方便、快捷地开发Java程序5.6 接口n在Java语言中,接口(Interface)是对符合接口需求的类的一套规范。接口与包相似,也是用来组织应用中的各类并调节它们的相互关系的一种结构,更准确地说,接口是用来实现类间多重继承功能的结构n1 1 接口概念接口概念接口主要作用是可以帮助实现类似于类的多重继承的功能。在Java中,出于简化程序结构的考虑,不再支持类间的多重继承而只支持单重继承,即一个类至多只能有一个直接父类。然而在解决实际问题的过程中,仅仅依靠单重继承在很多情况下都不能
28、将问题的复杂性表述完整,需要其他的机制作为辅助。 5.6 接口n2 2 接口声明接口声明Java中声明接口的语法如下:public interface 接口名 extends 父接口名列表 /接口体;/常量域声明 public static final 域类型 域名=常量值; /抽象方法声明 public abstract 返回值 方法名(参数列表) throw异常列表;从上面的语法规定可以看出,定义接口与定义类非常相似,实际上完全可以把接口理解成为一种特殊的类,接口是由常量和抽象方法组成的特殊类5.6 接口(1)接口中的属性都是用 final修饰的常量,(2)接口中的方法都是用abstrac
29、t修饰的抽象方法,在接口中只能给出这些抽象方法的方法名、返回值和参数列表,而不能定义方法体,即仅仅规定了一组信息交换、传输和处理的“接口”5.6 接口n2 2 接口的实现接口的实现一个类要实现某个或某几个接口时,有如下的步骤和注意事项: (1)在类的声明部分,用implements关键字声明该类将要实现哪些接口;如下:class 类名 implements 接口 5.6 接口(2)如果实现某接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法,即为所有抽象方法定义方法体,而且方法头部分应该与接口中的定义完全一致,即有完全相同的返回值和参数列表; (3)如果实现某
30、接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。(4)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头。(5)接口的抽象方法,其访问限制符都已指定是public,所以类在实现方法时,必须显式地使用public修饰符。 见书86例5。6 例5。7 例5。8 多态5.6 本章小结面向对象编程的优点之一是可通过继承性实现软件复用。新类通过继承性可继承父类已定义过的实例变量和方法。在这种情况下,新类叫做子类。单一继承是指一个子类继承一个父类。多重继承是指一个子类继承多个父类。Java不支持多重继承,但Java提供了接口。在子类中通常要加入它自己的实例变量和方法,所以子类一般
31、要比它的父类大。另一方面,子类比父类更具体,因而代表了较少的对象5.6 本章小结子类不能访问父类的private成员,但子类可以访问其父类的public,protected和包访问成员;要访问父类的包访问成员,子类一定要在父类的包内。子类构造函数总是先调用(显式的或隐式地)其父类的构造函数,以创建和初始化子类的父类成员。继承性实现了软件的复用,这不但节省了开发时间,也鼓励人们使用已经验证无误和调试过的高质量软件。子类的对象可以当作其父类的对象对待,反之则不行。5.6 本章小结父类和子类的关系是一种层次结构关系。当一个类使用继承性机制时,它可以是为其他类提供属性和行为的父类,也可以是继承那些属性
32、和行为的子类。继承性层次结构的层次数只受系统硬件结构的限制,但大多数层次结构只有几层。层次结构对于理解和管理复杂软件非常有用。随着软件复杂性的增加,Java提供了通过继承性和多态性支持层次结构的机制。protected访问是public和private访问之间一个保护性的中间层次。父类方法、子类方法和在同一个包内类的方法都能访问父类的protected成员,但其他方法均不能访问5.6 本章小结父类可以是一个子类的直接父类或间接父类。直接父类是子类显式使用extends说明的类,间接父类是子类从层次结构树的前几层上继承的类。当父类某成员不适合一个子类时,可以在子类中覆盖它。一个子类对象引用可以隐
33、式地转换成一个父类对象引用。使用显式的类型转换,可以把父类引用转换成子类引用。如果目标不是子类对象,将产生Class CastException例外处理。父类代表共性,从一个父类派生的所有类都继承了这个父类的功能。5.6 本章小结当通过父类引用调用一个方法时,Java会正确地选择与那个对象对应的类的覆盖方法。在多态性中,一个方法调用可能会产生不同动作,这取决于接受调用的对象类型。当从具体类(子类)中实例化某些对象时,这些引用可以用来对这些对象进行多态性操作。经常要把一些新类加入系统,这时可以用动态方法绑定(也称迟绑定)接纳它们。在编译方法调用时,不需要知道对象的类型。只有执行时,才会选择相应对
34、象的方法。 在动态方法绑定中,执行方法调用时系统将会找到接受调用对象所属的类的相应方法。对于父类提供的方法,子类可以覆盖它的父类版本。5.6 本章小结一个接口的定义由关键字interface开始以包含pubic final static数据。但这不是必须的,即子类 也 可 以 使 用 一 个 方 并 包 含 一 套 public abstract方法,接口也可为使用接口,一个类必须声明实现(关键字implements)接口,指定的参数个数和返回类型定义每个方法。当没有缺省的实现用来继承时,通常使用接口而不使用抽象类。当某个类实现一个接口时,也有同样的“是一个”继承性关系。并且必须根据接口中为实现一个以上的接口,只要在类定义中的关键字implements后面列出接口名逗号分隔。