640 likes | 809 Views
Java 软件设计基础. 5. 继承与多态. 类的封装. 类的特性 Java 程序设计的基本单位是类,类是用来创建具体对象的模板,有以下三个重要特性: 封装 Java 语言中,对象就是对一组变量和相关方法的封装 (Encapsulation) ,通过对对象的封装,实现模块化和信息隐藏。通过对类的成员辅以一定的访问限制,实现类中成员的信息隐藏。 继承 继承 (inheritance) 允许创建分等级层次的类,在 Java 中,所有的类都是直接或间接的继承 java.lang.Object 得到的。 多态
E N D
Java软件设计基础 5. 继承与多态
类的封装 类的特性 Java程序设计的基本单位是类,类是用来创建具体对象的模板,有以下三个重要特性: 封装 Java语言中,对象就是对一组变量和相关方法的封装(Encapsulation),通过对对象的封装,实现模块化和信息隐藏。通过对类的成员辅以一定的访问限制,实现类中成员的信息隐藏。 继承 继承(inheritance)允许创建分等级层次的类,在Java中,所有的类都是直接或间接的继承java.lang.Object得到的。 多态 多态(Polymorphism)是指同名的不同方法在类程序中共存,系统根据传递参数个数或类型的不同来决定调用哪一个方法。
类的封装 封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据不受外部干扰且不易被误用。 利用抽象数据类型将数据和基于数据的操作结合在一起,数据被保护在抽象数据类型的内部,系统的其他部分只有通过包裹在数据之外被授权的操作,才能够与这个抽象数据类型进行交互。 类是Java封装的基本单元。 一个类定义了将被一个对象集共享的结构和行为; 每一个给定的对象都包含这个类定义的数据和操作; 对象也被看成是类的实例(instance of a class); 类是逻辑结构,而对象是真正存在的物理实体。 封装可以提高程序中数据的完整性和安全性;开发过程的复杂性;减少出错可能;提高类或模块的可重用性。
访问权限 类中的每个方法或成员变量的访问权限都可以通过修饰符(public、private、protected、default)被标记。这种访问权限控制实现了一定范围内的信息隐藏。 private 被修饰的成员变量和方法称为私有变量和私有方法。 类中限定为private的成员变量和方法只能被这个类本身的方法访问,不能在类外通过名字来访问。 x has private access in A y( ) has private access in A
对于私有成员变量和方法,只有在本类中创建该类的对象时,这个对象才能访问自己的私有变量和类中的私有方法。对于私有成员变量和方法,只有在本类中创建该类的对象时,这个对象才能访问自己的私有变量和类中的私有方法。 同一个类的不同对象可以访问对方的私有变量或调用对方的私有方法,这是因为访问保护是控制在类的级别上,而不是对象的级别上。 在类内部创建对象,可以访问自己的私有变量和私有方法。
构造方法也可以限定为private,如果一个类的构造方法被声明为私有,则其他类不能生成该类的一个对象。构造方法也可以限定为private,如果一个类的构造方法被声明为私有,则其他类不能生成该类的一个对象。 • protected • 类中限定为protected的成员可以被这个类本身引用,被它的子类以及同一个包中所有其他的类访问。 编译报错
public 修饰的成员变量和方法被称为公共变量和公共方法,可被所有的类访问。 public方法的作用是让类的客户了解类提供的服务,即类的公共接口,而不必关心类是如何完成任务的。 friendly 这是缺省情况,限定为友好的成员可以被这个类本身和同一个包中的其他类访问,但是对于包外的所有类就不能访问。
5.2 类的继承 • 继承机制 • 继承是面向对象编程技术的一块基石。 • 继承能够创建一个通用类,并定义一系列相关项目的一般特性。该类可以被更具体的类继承。 • 每个具体的类都增加一些自己特有的东西。 • 继承是一个对象获得另一个对象的属性的过程。 • 由继承得到的类为子类(subclass或childclass),也被称为派生类(derived class)、扩展类(extended class)。 • 被继承的类为超类(superclass),也被称为基类(base class)或者父类(parent class),包括所有直接或间接被继承的类; • 类可以从另一个类的子类的子类的子类……继承而来,最终都是继承自最顶级的类Object。 • 定义在Object中的方法可被所有子类使用。
Object Number ImaginaryNumber ImaginaryNumber的间接父类 ImaginaryNumber的直接父类 • Java平台类层次结构:所有类都是Object类的后裔
继承支持按层次分类的概念。 • 简单的说,类的继承性就是新的子类可以从另一个父类派生出来,并自动拥有父类的全部属性和方法。 • 子类继承父类的成员变量与方法,就如同在子类中直接声明的一样,可以被子类中自己声明的任何实例方法所调用。 • 子类继承父类的状态和行为,同时也可以修改继承自父类的状态和行为,并添加新的状态和行为。通俗的说法:子类更具有特殊性。 • 单继承 • 使得代码更加可靠,不会出现因多个父类有相同的方法或属性所带来的麻烦。 继承用来为is-a关系建模。不要为了重用方法而盲目的派生一个类。例如,从Person类派生Tree类毫无意义。子类和父类之间必须存在is-a关系。
按层次分类 继承父类的属性与方法 添加新的特性(包括新的属性与方法)
创建子类 • 格式 • 说明 • 子类能够继承父类中public和protected成员变量和方法; • 如果子类和父类在同一个包内,子类能继承父类中用默认修饰符修饰的成员变量和方法; • 一般情况下,子类不能继承父类中的private成员变量和方法; • 这里要特别说明的是,因为嵌套类可以访问其外部类的所有私有成员,所以,若子类是继承自某公共或者受保护嵌套类,则可以间接访问(关于嵌套类,我们将在下节课中讲解)。 • 若缺省extends子句,则该类为java.lang.Object的子类。 [修饰符] class 子类名 extends 父类名{类体}
如果子类声明了一个与父类中的成员同名的成员,则子类不继承父类中的同名成员;如果子类声明了一个与父类中的成员同名的成员,则子类不继承父类中的同名成员; • 以上程序将打印“5”,表明子类Sub中并未继承父类的a。
构造方法 • 与属性和方法不同,父类的构造方法并不传给子类,它们只能从子类的构造方法中通过关键字super调用。 • 构造方法链 • 在任何情况下,构造一个类的实例时,将会沿着继承链调用所有父类的构造方法,父类的构造方法在子类的构造方法之前调用。 调用父类的构造方法必须使用关键字super,并且这个调用必须是构造方法的第一条语句。在子类中使用父类构造方法的名字会引起语法错误。
关键字super • Java中通过super来实现对父类成员的访问,super用来引用父类中的方法和变量,主要是用于继承被隐藏(hiding)的成员变量或者被覆盖的方法。 • 使用情况 • 访问父类被隐藏的成员变量,其引用格式为: • 调用父类被覆盖的方法,其引用格式为: • 调用父类的构造方法,其引用格式为: super.成员变量名 super.方法名([参数列表]) super([参数列表])
例程:自行车类 • 自行车类作为父类 三个属性:踏板步调、速度、档位 构造方法 提供四个方法: 设置踏板步调; 设置档位; 刹车减速; 提速;
自行车下面还有许多子类,如山地车、公路自行车等。自行车下面还有许多子类,如山地车、公路自行车等。 • 以山地车为例 除了拥有父类的三个属性,还有座高这一特有的属性 新增的方法:设置座高
this与super的总结 • this用来引用当前对象,super用来引用当前父类对象。用this和super区别分别出现在父类、子类中的同名成员变量和类中方法使用的同名局部变量。 子类隐藏了父类的成员变量 子类的方法遮蔽了子类的成员变量
this的使用 • 可以出现在实例方法和构造方法中,但不可以出现在类方法中。因为类方法可以直接通过类名直接调用,这时可能还没有任何对象诞生。 • this可以出现在类的构造方法中,代表使用该构造方法创建的对象。 代表创建的对象exa,这时number还未赋值; number赋值完毕;
this可以出现在类的实例方法中,代表使用该方法的当前对象。类的实例方法可以通过this调用该类的其他方法,或者成员变量。this可以出现在类的实例方法中,代表使用该方法的当前对象。类的实例方法可以通过this调用该类的其他方法,或者成员变量。 在某个对象调用f方法的过程中又调用了方法g。由于这种逻辑关系非常明确,所以当一个方法调用另一个方法时可以省略this,即本条语句等价于: g();
super的使用 • 用来访问父类被隐藏的成员变量,或调用父类被覆盖的方法和构造函数。
例程:成员变量的隐藏和方法的覆盖 父类的成员变量 父类的构造函数 父类的方法
子类的成员变量隐藏了父类的成员变量 子类的方法覆盖了父类的方法 子类的方法中调用了父类的方法
分析: • 根据构造方法链的原理,子类在实例化时,首先调用父类构造函数,实例化父类。之后才是子类自身实例化。 • 这就是为什么上例中会首先打印“in SuperClass:x=3”的原因。 • 将程序片段略作修改:
当运行主程序的“SubClass sub=new SubClass();”语句时,上述程序会报错,原因在于子类实例化之前要实例化父类,调用父类中的无参数构造方法,而父类中不存在无参数构造方法。 • 那么,父类中为什么会不存在无参数的构造方法呢? • 当构造一个类的对象时,编译器首先判断类是否有构造方法,如果没有,则自动加上一个默认构造方法;而一旦发现有构造方法后,则不再自动加上构造方法。 • 此时再用无参数的构造方法去创建对象,就会提示出错,因为已经没有系统提供的默认构造方法了。 如果一个类要生成其他子类,最好提供一个无参数的构造方法以避免编程错误。
在上述程序中,除了在父类中另外再定义一个无参数构造方法外,还可以显式的调用父类的构造函数,使用super语句。但是这一语句必须是子类构造函数的第一句。 • 改写如下:
总结 • 继承的最大好处就是类的可重用性。 • 当创建自己的类时,应尽可能将它作为某一个类的子类,在自己的子类中加入一些特殊的内容,而不必重新定义这个类所需的全部属性和行为。 • 初始化过程总是由高级到低级层次;资源回收过程应从低级到高级层次进行。
5.3 多态机制 • 多态 • 又被称为“一个方法名字,多个行为结果”。 • 通过方法覆盖(Override)和方法重载(Overload)来实现多态。 • 方法重载 • 一个类中可以有多个具有相同名字的方法,由传递给它们的不同个数和类型的参数来决定使用哪种方法。 • 通过重载可以定义同类的操作方法(行为),而不需要取很多个类似又容易混淆的名字。 • 方法的重载是功能多态性的体现。 • 功能多态性是指可以向功能传递不同的消息,以便让对象根据相应的消息来产生一定的行为。
重载方法需要满足以下条件: • 方法名相同; • 方法的参数签名(即参数类型、个数、顺序)不相同; • 方法的返回类型可以不相同,方法的修饰符也可以不相同; • 不能根据方法返回类型的不同来区分重载的方法。
根据不同的参数类型、个数、顺序进行判断,决定到底执行哪个方法。根据不同的参数类型、个数、顺序进行判断,决定到底执行哪个方法。 • 例程:希望根据接收到的不同数据作出不同的反应
方法覆盖 • 如果子类中实例方法的参数签名和返回类型与父类中的实例方法都一样,就称为子类中的方法覆盖了父类的方法。 • 通过方法覆盖,子类可以重新实现父类的某些实例方法,使其具有自己的特征。 • 覆盖方法也可以返回被覆盖方法的返回类型的子类型,称为协变返回类型(convariant return type)。 • 在方法覆盖中,子类需要保持与父类完全一致的方法头声明,否则就不是方法覆盖。有以下情况: • 参数个数、类型、顺序与父类完全相同,而返回类型类型不同:
area() in B cannot override area() in A; attempting to use incompatible return type
参数个数或参数类型与父类不尽相同,这时子类出现两个方法具有相同的名字,但保证了参数的不同,即子类出现了重载的方法:参数个数或参数类型与父类不尽相同,这时子类出现两个方法具有相同的名字,但保证了参数的不同,即子类出现了重载的方法: 合法覆盖父类的方法后,可以合法的定义另外的重载方法。
子类方法缩小了父类方法的访问权限: area() in B cannot override area() in A; attempting to assign weaker access privileges was public
方法的隐藏 • 如果子类定义的类方法的签名和父类中方法的签名相同,那么子类中的方法就隐藏了超类中的方法。 • 父类的静态方法不可以被子类隐藏为非静态,父类中的非静态方法不可以被子类覆盖为静态。 • 终止覆盖 • 为了防止利用属于系统重要信息的类来创建子类和进行方法覆盖,替换原有的类攻击系统,防止父类被覆盖,引入了仲只覆盖的理念,即final修饰符外延性的使用。 • 用final修饰类来避免产生子类,用final修饰符修饰父类中的方法就可以避免子类来覆盖此方法。 • 父类的私有方法是隐性final的,不能被子类覆盖。
5.4 toString方法 • Java中的每个类都源于java.lang.Object类。如果一个类在定义时没有指定继承,它的父类默认是Object。 • toString()方法 • 调用对象的toString()方法返回一个代表该对象的字符串。 • 默认情况下,返回一个由该对象所属的类名、@和该对象十六进制的散列码组成的字符串。
通常应该覆盖toString()方法,使它返回一个代表该对象的易懂的字符串。通常应该覆盖toString()方法,使它返回一个代表该对象的易懂的字符串。 也可以传递对象来调用: System.out.println(object) System.out.print(object) 这等价于调用: System.out.println(object.toString()) System.out.print(object.toString())
5.5 抽象类 • 引言 • 在继承的层次结构中,随着各新子类的出现,类变得越来越专门和具体。 • 类的设计应该保证父类包含子类的共同特征,有时,将一个父类设计的非常抽象,以至于它没有具体的实例,这样的类称为抽象类(abstract class)。 • 说明 • 抽象类和常规类一样具有数据和方法,但是不能用new操作符来创建它的实例; • 抽象方法只有方法头而没有实现,它的实现由子类提供。 • 子类可以是抽象的,即使它的父类是具体的。
非抽象类不能包含抽象方法,如果一个抽象父类的子类不能实现所有的抽象方法,它必须声明为抽象的。 抽象类不能用new操作符实例化,但仍然可以定义它的构造方法,可在子类的构造方法中调用。 包含抽象方法的类必须是抽象的。但是允许声明没有抽象方法的抽象类。
例 • 有以下形状类:
当涉及计算面积及周长时,其方法的实现取决于几何对象的具体类型。当涉及计算面积及周长时,其方法的实现取决于几何对象的具体类型。 • 当然,可也以将所有的形状归为一类,以方法的重载来计算形状的面积及周长,但这样使得类的特征不够明显。 • 可将getArea和getPerimeter方法定义为抽象方法,这些方法将在子类中实现。
子类继承抽象父类,增加自己特有的属性,并实现抽象父类的抽象方法。子类继承抽象父类,增加自己特有的属性,并实现抽象父类的抽象方法。
具体实例的创建 • 创建了一个新的圆形与新的矩形,并把它们赋值给变量s1和s2,这两个变量的类型是Shape。 • 当使用s1.getArea()时,由于s1是一个圆形,因此会自动调用Circle类中实现的getArea方法。 • 调用哪个方法由JVM在运行时根据对象的类型动态的决定。