1、第七章 类的多态性 钱庭霖 计算机工程系2 第7章 JAVA 多态性、抽象类与接口 掌握多态的优势和应用场合 实现多态 引用转型 多态性程序实例分析 掌握instanceof运算符的使用 抽象类与抽象方法 接口与接口的应用 继承是指从一个父类(父类)派生出派生类(子 类)的过程; 继承使用关键字extends; 对于类成员,可以使用public、protected、缺省 和private这4种访问权限修饰符; 对于类,可以使用public和缺省这2个访问权限; 创建子类实例时,必须先执行父类的构造方法, 然后再执行子类的构造方法; super关键字有两种主要用途,都与父类有关。 回顾为什么使用
2、多态? 宠物(Pet)生病了,需要主人给宠物 看病 不同宠物看病过程不一样 不同宠物恢复后体力值不一样打针 吃药 吃药 疗养 狗狗 Q仔 为什么使用多态? 编码实现 public class Master public void Cure(Dog dog) if (dog.getHealth() 50) dog.setHealth(60);System.out.println(“打针、吃药“);public void Cure(Penguin penguin)if (penguin.getHealth() 50) penguin.setHealth(70);System.out.println
3、(“吃药、疗养“); 主人类 Master master = new Master(); master.Cure(dog); master.Cure(penguin); 测试方法 为什么使用多态? 如果又需要给XXX看病,怎么办? 添加XXX类,继承Pet类 修改Master类,添加给XXX看病的方法使用多态优化设计 频繁修改代码,代码可扩展性、可维护性差 为什么使用多态? 使用多态优化后的代码 Pet pet = new Dog(); Master master = new Master(); master.Cure(pet); 测试方法 public class Dog extends P
4、et public void toHospital() this.setHealth(60);System.out.println(“打针、吃药“); public class Penguin extends Pet public void toHospital() this.setHealth(70);System.out.println(“吃药、疗养“); public class Master public void Cure(Pet pet) if (pet.getHealth() 50)pet.toHospital(); 主人类 Dog类 Penguin类 1 2 3 4 又要给XX
5、X看病时,只需: 1. 编写XXX类继承Pet类(旧方案也需要) 2. 创建XXX类对象(旧方案也需要) 3. 其他代码不变(不用修改Master类)什么是多态? 生活中的多态 你能列举出一个多态的生活示例吗? 程序中的多态同一个引用类型,使用不同的实例而执行不同操作 父类引用, 子类对象 同一种事物,由于条件不同,产生的结果也不同9 多态性(POLYMORPHISM) 概念:是面向对象程序设计的另一个重要特征,其基本含 义是“拥有多种形态”,具体指在程序中用相同的名称来 表示不同的含义。例如:用同一方法名来表示不同的操 作。 类型:有两种 n 静态多态性:包括变量的隐藏、方法的重载 n 动态
6、多态性:在编译时不能确定调用方法,只有在运行时才 能确定调用方法,又称为运行时的多态性。静态多态 静态多态也称为编译时多态,即在编译时决定 调用哪个方法; 静态多态一般是指方法重载; 只要构成了方法重载,就可以认为形成了静态 多态的条件; 静态多态与是否发生继承没有必然联系。动态多态 动态多态也称为运行时多态,即在运行时才能 确定调用哪个方法; 形成动态多态必须具体以下条件: 必须要有继承的情况存在; 在继承中必须要有方法覆盖; 必须由父类的引用指向派生类的实例,并且通过父类的引 用调用被覆盖的方法; 由上述条件可以看出,继承是实现动态多态的 首要前提。12 下面主要介绍动态多态性 /多态性的
7、例子 class Animal public void roar() System.out.println(“动物:.“); class Cat extends Animal public void roar() System.out.println(“猫:喵,喵,喵,.“); class Dog extends Animal public void roar() System.out.println(“狗:汪,汪,汪,.“); 13 /多态性的例子(续) public class AnimalTest public static void main(String args) Animal a
8、m=new Animal(); am.roar(); am=new Dog(); am.roar(); am=new Cat();am.roar(); 程序运行结果: 动物:. 狗:汪,汪,汪,. 猫:喵,喵,喵,. 根据对象的赋值规则,可以把子类对象赋给父 类对象名,这是允许。当用同一形式: 父类对象名.roar() 去调用时,能够根据子类对象 的不同,得到不同的结果,这就是多态性。 实现多态的流程 用多态实现打印机 分为黑白打印机和彩色打印机 不同类型的打印机打印效果不同 黑白打印机 彩色打印机 打印实现多态的设计思路 使用多态实现思路 编写父类 编写子类,子类重写(覆盖)父类方法 运行时
9、,使用父类的类型,子类的对象 计算机可以连接各种打印机 无论连接何种打印机打印方法都相同 根据连接打印机不同,效果也不同 实现多态 编码实现实现多态的两个要素: 1. 方法重写 2. 使用父类类型class Printer()print(String str); class ColorPrinter extends Printer print(String str) System.out.println(“输出彩色的“+str); class BlackPrinter extends Printer print(String str) System.out.println(“输出黑白的“+st
10、r); public static void main(String args) Printer p = new ColorPrinter();p.print();p = new BlackPrinter();p.print(); 父类 子类 运行 同一种操作 方式,不同 的操作对象 只能调用父 类已经定义 的方法 继承是子类使用父类的方法,而多态则 是父类使用子类的方法。 (把父类当做 子类来用)方法重写(覆盖) 方法重写的规则 在继承关系的子类中 重写的方法名、参数、返回值类型必须与父类相 同 私有方法不能继承因而也无法重写位置 方法名 参数表 返回值 访问修饰符 方法重写 子类 相同 相
11、同 相同 不能比父类更严格 方法重载 同类 相同 不相同 无关 无关方法重写 override 方法重载overload VS18 实现运行时多态技术的条件: n 有一个继承层次关系; n 在子类中重写父类的方法; n 通过父类的引用对子类对象进行调用。 n 采用多态技术的优点: 引进多态技术之后,尽管子类的对象千差万别,但都 可以采用 父类引用.方法名(参数) 统一 方式来调用,在 程序运行时能根据子对象的不同得到不同的结果。这种“以 不变应万变”的形式可以规范、简化程序设计,符合软件工 程的“一个接口,多种方法”思想。引用转型 父类的引用可以指向子类的对象,如: BaseClass obj
12、 = new DerivedClass(); 这样的语句是合法的; 但是子类的引用则不可以指向父类的对象,如 : DerivedClass obj = new BaseClass(); 这样的语句将引发错误。引用转型示例 class Person /定义人类 class Student extends Person /学生类继承于人类 public class OverriddenDemo public static void main(String args) /正确,所有的学生一定是人Person per = new Student();/错误,并不是所有的人都是学生Student std
13、 = new Person(); 多态性实例分析 Example 例如动物园的饲养员能够给各种各样的动物喂食。图1显示了饲养员Feeder、食物Food和动物Animal以及它的子类的类 框图。多态性实例分析 图1 饲养员Feeder、食物Food和动物Animal以及它的子类的 类框图多态性实例分析 可以把Feeder、Animal和Food都看成独立的子系统。Feeder类的定义 如下: public class Feeder public void feed(Animal animal,Food food) animal.eat(food); 以下程序演示一个饲养员分别给一只狗喂肉骨头,
14、给一只猫喂鱼。 Feeder feeder=new Feeder(); Animal animal=new Dog(); Food food=new Bone(); feeder.feed(animal,food); animal=new Cat(); food=new Fish(); feeder.feed(animal,food); /给狗喂肉骨头 /给猫喂鱼多态性实例分析 Java语言允许某个类型的引用变量引用子类的实例,而且可以对 这个引用变量进行类型转换: Animal animal=new Dog(); Dog dog=(Dog)animal; 如图2所示,如果把引用变量转换为子类
15、类型,称为向下转型, 如果把引用变量转换为父类类型,称为向上转型。在进行引用变 量的类型转换时,会受到各种限制。而且在通过引用变量访问它 所引用的实例的静态属性、静态方法、实例属性、实例方法,以 及从父类中继承的方法和属性时,Java虚拟机会采用不 同的绑 定机制。 /向下转型,把Animal类型转换为Dog类型/向上转型,把Animal类型转换为Dog类型多态 图2 类型转换多态性实例分析 下面通过具体的例子来演示多态的各种特性。 在下面的例程中,Base父类和Sub子类中都定义了实例变量var、实例方法 method()、静态变量staticVar和静态方法staticMethod(),此
16、外,在Sub 类中还定义了实例变量subVar和subMethod()。 Sub.java package poly; class Base String var=“BaseVar“; static String staticVar=“StaticBaseVar“; void method() System.out.println(“Base method“); static void staticMethod() System.out.println(“Static Base method“); /成员(实例)变量 /静态变量 /成员(实例)方法 /静态方法 public class Sub
17、 extends Base String var=“SubVar”; /成员(实例)变量 static String staticVar=“StaticSubVar“; /静态变量 void method() /覆盖父类的method()方法 System.out.println(“Sub method“); static void staticMethod() /隐藏父类的staticMethod()方法 System.out.println(“Static Sub method“); String subVar=“Var only belonging to Sub“; void subMe
18、thod() System.out.println(“Method only belonging to Sub“); public static void main(String args) Base who=new Sub(); /who被声明为Base类型,引用Sub实例 System.out.println(“who.var=“+who.var); System.out.println(“who.staticVar=“+who.staticVar); who.method(); who.staticMethod(); /打印Base类的var变量 /打印Base类的staticVar变量
19、 /打印Sub实例的method()方法 /打印Base类的staticMethod()方法多态的一些细节 (1)对于一个引用类型的变量,Java编译器按照它声 明的类型来处理。 例如在以下代码中,编译器认为who是Base类型的引用变量,不存在 subVar成员变量和subMethod()方法,所以编译出错: Base whonew Sub(); /who是Base类型 who.subVar=“123”; /编译出错,提示在Base类中没有subVar属性 who.subMethod(); /编译出错,提示在Base类中没有subMethod()方法 如果要访问Sub类的成员,必须通过强制类
20、型的转换: Base whonew Sub(); /who是Base类型 (Sub)who).subVar=“123“; /编译成功,把Base引用类型强制转换为Sub引用类型 (Sub)who).subMethod(); /编译成功,把Base引用类型强制转换为Sub引用类型多态的一些细节 Java编译器允许具有直接或间接继承关系的类之间进行类型转换. 对于向上转型,不必使用强制类型转换,因为子类的对象肯定也可看作父类的对 象。例如一个Dog对象是一个Animal对象,也是一个Creature对象,也是一个 Object对象: Dog dog=new Dog(); Creature crea
21、ture=dog; /编译成功,把Dog引用类型直接转换为Creature引用类型 Object object=dog; /编译成功,把Dog引用类型直接转换为Object引用类型 对于向下转型,必须进行强制类型转换: Creature creature=new Cat(); Animal animal=(Animal)creature; /编译成功,把Creature引用类型强制转 换为Animal引用类型 Cat cat=(Cat)creature; /编译成功,把Creature引用类型强制转换为Cat引用类型 Dog dog=(Dog)creature; /编译成功,把Creature
22、引用类型强制转换为Dog引用类型多态的一些细节 假如两种类型之间没有继承关系,即不在继承树的同一个继承分支 上,那么Java编译器不允许进行类型转换,例如: Dog dog=new Dog(); Cat cat=(Cat)dog; /编译出错,不允许把Dog引用类型转换为Cat引用类型 (2)对于一个引用类型变量,运行时Java虚拟机按照它实际引用 的对象来处理。例如以下代码虽然编译可以通过,但运行时会抛出 ClassCastException运行时异常: Base who=new Base(); /who引用Base类的实例 Sub s=(Sub)who; /运行时抛出ClassCastEx
23、ception 在运行时,子类的对象可以转换为父类类型,而父类的对象实际上无法转 换为子类类型。因为通俗的讲,父类拥有的成员子类肯定也有,而子类拥 有的成员父类不一定有。多态的一些细节 假定Java虚拟机能够把子类对象转换为父类类型,那么以下代码中的 sub.subMethod()方法无法执行: Base who=new Base(); /who引用Base类的实例 Sub sub=(Sub)who; /假定运行时未出错 sub.subMethod(); /sub引用变量实际上引用Base实例,而Base实例没 有subMethod()方法 由此可见,在运行时,Java虚拟机无法把子类对象转变
24、为父类类型。以下 代码尽管能够编译成功,但在运行时,creature变量引用的Cat对象无法 转变为Dog类型,因此会抛出ClassCastException: Creature creature=new Cat(); Animal animal=(Animal)creature; /运行正常,Cat对象可转换为Animal类型 Cat cat=(Cat)creature; /运行正常,Cat对象可以被Cat类型的引用变量引用 Dog dog=(Dog)creature; /运行时抛出ClassCastException,Cat对象不可转换 为Dog类型 (但是会编译成功) (3)在运行时环境
25、中,通过引用类型变量来访问所引用对象的方法 和属性时,Java虚拟机采用以下绑定规则: 1)成员(实例)方法与引用变量实际引用的对象的方法绑定,这种 绑定属于动态绑定,因为是在运行时由Java虚拟机动态决定的。 2)静态方法与引用变量所声明的类型的方法绑定,这种绑定属于静 态绑定,因为实际上是在编译阶段就已经作了绑定。 3)成员变量(包括静态变量和实例变量)与引用变量所声明的类型 的成员变量绑定,这种绑定属于静态绑定,因为实际上是在编译阶 段就已经作了绑定。 多态的一些细节 例如,对于以下这段代码: Base who=new Sub(); /who被声明为Base类型,引用Sub实例对象 Sy
26、stem.out.println(“who.var=“+who.var); /打印Base类的var变量 System.out.println(“who.staticVar=“+who.staticVar); /打印Base类的staticVar变量 who.method(); /打印Sub实例的method()方法 who.staticMethod(); /打印Base类的staticMethod()方法 运行时将会输出如下结果: who.var=BaseVar who.staticVar=StaticBaseVar Sub method Static Base method 多态的一些细节
27、 再看一个例子: public abstract class A abstract void method(); void test() method(); /到底调用哪个类的mehtod()方法? public class B extends A void method() /覆盖父类的method()方法 System.out.println(“Sub“); public static void main(String args) new B().test(); 多态的一些细节 运行类B的main()方法将打印“Sub“。 方法test()在父类A中定义,它调用了方法method()。虽然
28、方法method() 在类A 中被定义为是抽象的,它仍然可以被调用,因为在运行时环境中, Java虚拟机会执行类B的实例的method()方法。一个实例所属的类肯定实 现了父类中 所有的抽象方法(否则这个类不能被实例化)。 再看一个例子: public class A void method()System.out.println(“Base“); void test()method(); public class B extends A void method()System.out.println(“Sub“); public static void main(String args) n
29、ew A().test(); /调用类A的method()方法 new B().test(); /调用类B的method()方法 多态的一些细节 运行这段代码将打印: Base Sub test()方法在父类A中定义,它调用了method()方法,和上面一个 例子的区别是父类A的method()方法不是抽象的。但是通过new B().test()调用method()方法,执行的仍然是子类B的method()方 法。由此可以更深入地体会动态绑定的思想:在运行环境中,当通 过 B类的实例去调用一系列的实例方法(包括一个方法调用的另一 个方法),将优先和B类本身包含的实例方法动态绑定,如果B类没 有定义这个实例方法,才会和从 父类A中继承来的实例方法动态绑 定。 多态的一些细节