590 likes | 721 Views
第六章 深入面向对象的程序设计. 6.1 继承与重载 6.1.1 继承关系的定义 Java 中的继承是通过 extends 关键字来实现的。 新定义的类称为子类,它可以从父类那里继承所有的 非 private 的属性和方法作为自己的成员。 见例类 Employee 、 CommonEmployee 、 ManagerEmployee. 6.1.2 属性的继承与隐藏. 1. 属性的继承 子类可以继承父类非私有的所有属性。 子类可以自己新建一个属于自己的属性。 子类的全部属性=父类继承下来的+自己新建的 。 说明:
E N D
第六章 深入面向对象的程序设计 • 6.1 继承与重载 • 6.1.1 继承关系的定义 • Java中的继承是通过extends关键字来实现的。 • 新定义的类称为子类,它可以从父类那里继承所有的非private的属性和方法作为自己的成员。 • 见例类Employee、CommonEmployee、ManagerEmployee
6.1.2 属性的继承与隐藏 • 1.属性的继承 • 子类可以继承父类非私有的所有属性。 • 子类可以自己新建一个属于自己的属性。 • 子类的全部属性=父类继承下来的+自己新建的。 • 说明: • 父类的所有非私有属性实际是各子类都拥有的属性的集合。 • 子类从父类继承属性不需要把父类属性的定义部分再复制一遍。 • 优点:减少程序的维护量。
2.属性的隐藏 • 子类从父类继承来的属性变量重新加以定义,称为属性的隐藏。 • 见例6.1TestHide.java
6.1.3 方法的继承、重载和覆盖 • 1.方法的继承 • 父类的非私有方法作为类的非私有成员,也可以被子类所继承。 • 见例6.2 InheritMethod.java
2.方法的覆盖 • 在子类中定义与父类相同的方法名称,称为方法的覆盖。 • 见例6.3OverrideMethod.java • 注: • 在方法的覆盖中,由于同名方法隶属于不同的类,所以要解决调用如何区分他们的问题,只需要在方法名前面使用不同的类名或不同类的对象名即可。 • 子类在重新定义父类已有的方法时,应保持与父类完全相同的方法头声明,即应与父类有完全相同的方法名、返回值和参数列表。
3.方法的重载 • 子类定义同名方法来覆盖父类的方法是面向对象方法中多态技术的一种实现。 • 所谓多态是指同名的不同方法共存。有助于隐藏对象内部的细节,提高程序的抽象程度,是面向对象程序设计中经常使用的方法。 • 实现多态技术的另一种重要的手段就是方法的重载。 • 重载:类对自身已有的同名方法的重新定义。重载与覆盖不同,重载不是子类对父类同名方法的重新定义。
方法的重载是同类中同名方法共存的情况,不能像方法的覆盖那样采用类名前缀来区分,而应该采用通过参数列表来区分不同的方法。方法的重载是同类中同名方法共存的情况,不能像方法的覆盖那样采用类名前缀来区分,而应该采用通过参数列表来区分不同的方法。 • 同类中同名的不同方法需要有不同的参数列表,包括参数顺序的不同和参数类型的不同。
6.1.4 父类对象与子类对象的使用与转化 • super 和this是常用来指代父类对象和子类对象的关键字。 • Java系统默认,每个类都缺省地具有null、this和super三个量,所以在任意类中都可以不加说明而直接使用它们。 • null代表“空”,代表一个什么也没有的“空”值,一般用null来代表尚未存在的对象,在定义一个对象但尚未为其开辟内存单元时可以指定这个对象为null。
1.this • this表示的是当前对象本身, 更准确地说,this代表了当前对象的一个引用。 • 对象的引用可以理解为对象的另一个名字,通过引用可以顺利地访问到对象,包括修改对象的属性、调用对象的方法。
this通常用来把当前对象的引用作为参数传递给其他的对象或方法。如:this通常用来把当前对象的引用作为参数传递给其他的对象或方法。如: • class SecretaryEmployee extends CommonEmployee { //一般雇员的子类:秘书类 • SecretaryEmployee(ManagerEmployee boss){ //秘书的构造函数 • m_EmpSalary=boss.getSalary()/3; //秘书的薪金是上司的三分之一 • } • }
//雇员的子类2:主管 • class ManagerEmployee extends Employee{ • SecretaryEmployee m_Secretary; //主管的秘书 • ManagerEmployee(Date mgrdate){ //主管的构造函数 • m_Secretary=new SecretaryEmployee(this); //每新任命一位主管,都为他指派一名秘书 • } • } • 说明:每新建一个ManagerEmployee对象时,都同时创建一个SecretaryEmployee对象作为其秘书。
2.super • super表示的是当前对象的直接父类对象,是当前对象的直接父类对象的引用。 • 所谓直接父类是相对于当前对象的其他“祖先”类而言。
注意: • this和super是属于类的所有特指的属性(即类的成员),只能用来代表当前对象和当前对象的父对象,而不能像其他类的属性一样随意引用。下列都是错误的代码: • ManagerEmployee MgrEmp=new ManagerEmployee(); • MgrEmp.this.getMinSalary();//Error • MgrEmp.super.workDays();//Error
3.父类对象与子类对象的转换 • 父类对象和子类对象的转化需要注意如下的原则: • 子类对象可以被视为是其父类的一个对象; • 父类对象不能被当作是其某一个子类的对象; • 如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数; • 如果父类对象引用指向的实际是一个子类对象,那么这个父类对象的引用可以用强制类型转换成子类对象的引用。
例: • class superClass { //定义父类 • int x; • ... • } • class SubClass extends SuperClass { //定义子类 • int y; • char ch; • ... • }
public class UseSuperSub { //使用父类与子类 • SuperClass sc,sc_ref; • SubClass sb,sb_ref; • sc=new SuperClass(); • sb=new SubClass(); • sc_ref=sb; //父类引用可以指向子类对象 • sb_ref=(SubClass)sc_ref; //父类引用转换成子类引用 • }
6.1.5 构造函数的继承与重载 • 构造函数是类的一种特殊函数,它也可以从父类那里继承,也可以互相重载。 • 1.构造函数的重载 • 构造函数的重载是指同一个类中存在着若干个具有不同参数列表的构造函数。 • 当一个类因构造函数的重载而存在着若干个构造函数时,创建该类对象的语句会自动根据给出的实际参数的数目、类型和顺序来确定调用哪个构造函数来完成新对象的初始化工作。
当一个构造函数需要调用另一个构造函数时,应使用关键字this,同时这个调用语句应该是整个构造函数的第一个可执行语句。如:当一个构造函数需要调用另一个构造函数时,应使用关键字this,同时这个调用语句应该是整个构造函数的第一个可执行语句。如: • Employee(){ • m_EmpNo=m_NextEmpNo++; • } • Employee(String name){ • this(); • m_EmpName=new String(name); • }
2.构造函数的继承 • 子类可以继承父类的构造函数,遵循以下的原则: • 1,子类无条件地继承父类的不含参数的构造函数; • 2,如果子类自己定义了构造函数,则它在创建新对象时,它将先执行继承自父类的无参数构造函数,然后再执行自己的构造函数; • 3,对于父类的含参数构造函数,子类可以通过在自己构造函数中使用super关键字来调用它,但这个调用语句必须是子类构造函数的第一个可执行语句。
在父类已有构造函数的基础上,子类的构造函数可以有如下的几种设计方法:在父类已有构造函数的基础上,子类的构造函数可以有如下的几种设计方法: • 1,不专门定义自己的构造函数,每当创建对象时,系统自动调用父类的无参数构造函数 • 2,定义自己的构造函数并调用父类的含参数构造函数,在父类构造函数的初始化操作的基础上定义子类自己的初始化操作。 • 3,实现构造函数的重载,满足多层次的对象初始化需要
6.2 包(package) • 包: • 1,把各种类组织在一起,使得程序功能清楚、结构分明。 • 2,包是一种松散的类的集合。同一包中的类之间可以是任意的关系,在缺省情况下可以互相访问。 • 3,使用包有利于实现不同程序间类的复用。
1.创建包 • 缺省:系统为每一个.java源文件创建一个无名包。 • (1)该.java文件中定义的所有类都隶属于这个无名包,它们之间可以相互引用。 • (2)无名包不能被其他包所引用,即无名包中的类不能被其他包中的类所利用和复用。
创建包的语句需要用关键字package,而且是整个.java文件的第一个语句。创建包的语句需要用关键字package,而且是整个.java文件的第一个语句。 • 格式: • package 包名; • package pkg1[.pkg2[.pkg3]]; • 例:package EmpClasses; • package EmpSystem.EmpClasses;//创建两个子文件夹 • 注:实际上,创建包就是在当前文件夹下创建一个子文件夹,以便存放这个包中包含的所有类的.class文件。 • 命令行创建包:Javac –d . *.java
2.包的引用 • 将类组织成包的目的是为了更好地利用包中的类。 • 引用不在同一个包中的其它(非)public类,可以使用下面几种方法: • (1)使用包名、类名前缀 • 对于同一个包中的其他类,只需在要使用的属性或方法名前加上类名作为前缀即可; • 对于其他包中的类,则需要在类名前缀的前面加上包名前缀。
(2)加载需要使用的类 • 在程序的开始部分利用import语句将需要使用的整个类加载到当前程序中,这样在程序中需要引用这个类的地方就不需要再使用包名作为前缀即可。 • 可以直接利用import语句引入整个包。 • Import java.awt.*;
(4)CLASSPATH • 环境变量CLASSPATH类似于DOS操作系统中的PATH,它指明了所有缺省的类字节码文件路径。当一个程序找不到它所需使用的其他类的.class文件时,系统会自动到CLASSPATH环境变量所指明的路径中去寻找。
6.3 接口 • 接口在有些资料上被称为界面,英文名称为interface。 • 接口与包相似,也是用来组织应用中的各类并调节它们的相互关系的一种结构。 • 接口是用来实现类间多重继承功能的结构。
6.3.1 接口的基本概念 • Java中的接口定义了若干的抽象方法和常量,形成一个属性集合,该属性集合通常代表了某一组功能的实现,其主要作用是可以帮助我们实现类似于类的多重继承的功能。 • 多重继承是指一个子类可以有一个以上的直接父类,该子类可以继承它所有直接父类的成员。
说明: • Java中的接口中的属性都是常量,接口中的方法都是没有方法体的抽象方法。 • 接口定义的仅仅是实现某一特定功能的一组功能的对外接口和规范。 • 接口并没有真正地实现这个功能,这个功能的真正实现是在“继承”了这个接口的各个类中完成的,由这些类来具体定义接口中各抽象方法的方法体。 • 因此,在Java中,通常把对接口功能的“继承”称为“实现”。
6.3.2 接口的声明 • Java中声明接口的语法如下: • [public] interface 接口名 [extends 父接口名列表] • { //接口体 • //常量域声明 • [public][static][final]域类型 域名=常量值; • //抽象方法声明 • [public][abstract][native]返回值 方法名(参数列表)[throw 异常列表]; • }
接口实际上就是一种特殊的类; • 接口是由常量和抽象方法组成的特殊类。 • 一个类只能有一个父类,但是可以同时实现若干个接口。 • 用public修饰的接口是公共接口,可以被所有的类和接口使用,而没有public修饰符的接口则只能被同一个包中的其他类和接口利用。
接口也具有继承性。定义一个接口时可以通过extends关键字声明该新接口是某个已经存在的父接口的派生接口,它将继承父接口的所有属性和方法。接口也具有继承性。定义一个接口时可以通过extends关键字声明该新接口是某个已经存在的父接口的派生接口,它将继承父接口的所有属性和方法。 • 一个接口可以有一个以上的父接口,它们之间用逗号分隔,形成父接口列表。新接口将继承所有父接口中的属性和方法。
接口体的声明是定义接口的重要部分,接口体由两部分组成,一部分是对接口中属性的声明,另一部分是对接口中方法的声明。接口体的声明是定义接口的重要部分,接口体由两部分组成,一部分是对接口中属性的声明,另一部分是对接口中方法的声明。 • 属性都是用final修饰的常量 • 方法都是用abstract修饰的抽象方法,在接口中只能给出这些抽象方法的方法名、返回值和参数列表,而不能定义方法体。
接口中的所有属性都必须是public static final,这是系统默认的规定,所以接口属性也可以没有任何修饰符,其效果完成相同。 • 同样,接口中的所有方法都必须是默认的public abstract,无论是否有修饰符显示地限定它。
例: • public interface Awardable{ • final double m_MaxAwardRate=0.1; //最高提成比例 • final double m_MinAwardRate=0.002; //最低提成比例 • abstract double getAwardRate(); //获得提成比例 • abstract boolean setAwardRate(double newrate); //设置提成比例 • abstract void caculateAward(); //计算提成数额 • abstract double getAward(); //获取提成数额 • }
6.3.3 接口的实现 • 一个类要实现某个或某几个接口时,有如下的步骤和注意事项: • (1)在类的声明部分,用implements关键字声明该类将要实现哪些接口; • (2)如果实现某接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法; • (3)若实现某接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。但是对于这个抽象类任何一个非抽象的子类而言,它们父类所实现的接口中的所有抽象方法都必须有实在的方法体,这些方法体可以来自抽象的父类,也可以来自子类自身,但是不允许存在未被实现的接口方法。主要体现了非抽象类中不能存在抽象方法的原则。 • (4)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头。 • (5)接口的抽象方法,其访问限制符都已指定是public,所以类在实现方法时,必须显示地使用public修饰符。
6.4 错误、异常及其处理 • 6.4.1 编程中的错误 • 错误是编程中不可避免和必须要处理的问题。 • 一般来说错误分为编译错误和运行错误两种。
1.编译错误 • 编译错误是由于所编写的程序存在的语法问题,未能通过由源代码到目标码(在Java语言中是由源代码到字节码)的编译过程而产生的,它是由语言的编译系统负责检测和报告。
2.运行错误 • 运行错误是在程序的运行过程中产生的错误。根据性质不同,运行错误还可分为系统运行错误和逻辑运行错误。 • 系统错误是指程序在执行过程中引发了操作系统的问题。 • 逻辑运行错误是指程序不能实现编程人员的设计意图和设计功能而产生的错误。
如何排除运行错误? • 排除运行错误,包括系统运行错误和逻辑运行错误,一个非常有效和常用的手段是使用开发环境所提供的单步运行机制和设置断点功能来分解程序运行过程,使之在人为的控制下边调试边运行。 • 在调试过程中,调试者可以随时检查变量中保存的中间量,设置临时环境,一步步地检查程序的执行过程,从而挖出隐藏的错误。
6.4.2 异常与异常类 • 异常(Exception),又称为例外,是特殊的运行错误对象,对应着Java语言特定的运行错误处理机制。 • Java中定义了很多异常类,每个异常类都代表了一种运行错误,类中包含了该运行错误的信息和处理错误的方法等内容。 • 每当Java程序运行过程中发生一个可识别的运行错误时,即该错误有一个异常类与之相对应时,系统都会产生一个相应的该异常类的对象,即产生一个异常。
1.异常类结构与组成 • Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种特定的运行错误。所有的Java异常类都是系统类库中的Exception类的子类。
Exception类 • Exception类有自己的方法和属性,它的构造函数为: • public Exception(); • public Exception(String s); • 第二个构造函数可以接受字符串参数传入的信息,该信息是对该例外所对应的错误的描述。 • 常用方法包括: • public String toString();//返回描述当前Exception类信息的字符串 • public void printStackTrace();//功能是完成一个打印操作,在当前的标准输出上打印输出当前例外对象的堆栈使用轨迹,也即程序先后调用执行了哪些对象或类的哪些方法,使得运行过程中产生了这个例外对象。
2.系统定义的运行异常 • Exception类有若干子类,每一个子类代表了一种特定的运行时错误。 • 这些子类有些是系统事先定义好并包含在Java类库中的,称为系统定义的运行异常。 • 由于预先定义了相应的异常,java程序即使产生一些致命的错误,系统也会自动产生一个对应的异常对象来处理和控制这个错误,避免其蔓延或产生更大的问题。
3.用户自定义的异常 • 对于某个应用所特有的运行错误,则需要编程人员根据程序的特殊逻辑在用户程序里自己创建用户自定义的异常类和异常对象。这种用户自定义异常主要用来处理用户程序中特定的逻辑运行错误。
public class IllegalSalaryException extends Exception{ • //产生当前错误的Employee对象的引用 • private Employee m_ConcernedEmp; • private double m_IllegalSalary; //产生当前错误的非法的工资数额 • IllegalSalaryException(){ • super("不合法的起薪:低于最低工资限额"); //调用父类Exception的构造函数 • } • IllegalSalaryException(Employee emp,double iSal){ //构造函数 • this(); • m_ConcernedEmp=emp; //初始化两个属性 • m_IllegalSalary=iSal; • }
public String toString(){ • String s; • if (m_ConcernedEmp!=null) • s="为雇员提供非法工资:雇员号-"+m_ConcernedEmp.getEmpNo()+"非法工资数额-"+m_IllegalSalary+"合法的最低工资数额-"+Employee.getMinSalary(); • else • s="给出的工资初始值不合理,不能创建Employee对象"; • return s; • } • }
创建用户自定义异常时,一般需要完成如下的工作:创建用户自定义异常时,一般需要完成如下的工作: • (1)声明一个新的异常类,使之以Exception类或其他某个已经存在的系统异常类或用户异常类为父类; • (2)为新的异常类定义属性和方法,或重载父类的属性和方法,使这些属性和方法能够体现该类所对应的错误的信息。
6.4.3 异常的抛出 • Java程序在运行时如果引发了一个可识别的错误,就会产生一个与该错误相对应的异常类的对象,这个过程被称为异常的抛出。
1.系统自动抛出异常 • 所有的系统定义的运行异常都可以由系统自动抛出。例: • public class TestSystemException • { • public static void main(String[] args) • { • int a=0,b=5; • System.out.println(b/a); • } • }