1、第六章:多态,多态, 多态 向上转型和动态绑定 构造器和多态,复习, 面向对象的三大基本特征 抽象与封装、继承、多态,复习,1、 抽象与封装 抽象:抓住事物的主要特征 静态特征数据,动态特征方法,复习, 封装:将这些数据和方法封装在一起,形成类 通过封装和访问控制,可以对类中的数据成员和成员函数起到保护作用 接口与实现的第一次分离,复习,2、 继承 继承的作用 通过继承,可以以现有类为基础,派生出新类,达到代码重用的目的,复习, 继承的本质 将从同一基类导出的多种类型视为同一类型来处理,而同一份代码就可以毫无区别地运行在这些不同的类型之上了,复习,数据成员,成员函数,多态,3、 多态(poly
2、morphism ) 多态的作用 多态是面向对象最核心的机制,多态使程序的可扩展性达到了极至,多态, 多态的本质 多态方法调用允许一种类型表现出与其它相似类型之间的区别,只要它们都是从同一基类导出来的,多态, 多态的例子: Shape基类和它的若干个派生类 分析其draw方法,多态,Circle(圆形),Square(方形),Triangle(三角形),数据成员,成员函数 (接口),多态, 继承与多态的重要区别 继承侧重于:父类的成员将原封不动的遗传给子类 多态侧重于:父类的成员将会在子类中被覆盖,即父类的成员在子类中具有各种各样的形态,多态, 多态的概念 概念之一: 多态包括两种情况:重载与
3、覆盖,多态, 概念之二: 多态,也称为动态绑定,指的是在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,多态, 当你调用父类里面被覆盖方法的时候, 实际当中new的是哪个对象, 就调用当前这个对象(子类对象)的与之相关的覆盖方法 使用多态的三个条件:继承、覆盖与向上转型 例子:使用上面的Shape及其子类作出初步分析,向上转型,1、 以乐器作为典例开始分析 class Instrument public void play()System.out.println(“Instrument”);,向上转型, public class Wind extends I
4、nstrument / 音乐分为管乐、弦乐、鼓乐等等public void play()/ 覆盖基类继承下来的方法 / 相关的覆盖方法System.out.println(“Wind”);,向上转型, public class Music public static void tune(Instrument i)i.play(); / 调音public static void main(String args) Wind flute = new Wind(); / 长笛tune(flute); / 向上转型,向上转型, 分析: 如果Instrument类只有一个子类,确实应该这样改写,这时没有
5、使用向上转型的丝毫理由 问题是:一个基类绝非只有一个子类,而如果不采用向上转型,我们的代码就存在着很多的隐患和缺陷(通过下面例子可以看到这一点),向上转型,class Instrument public void play() / 演奏 System.out.println(“Instrument”);,向上转型, public class Wind extends Instrument / 音乐分为管乐、弦乐、鼓乐等等public void play()/ 覆盖基类继承下来的方法 System.out.println(“wind”);,向上转型, / 增加Stringed(弦乐)类和Bras
6、s(铜管乐)类public class Stringed extends Instrument public void play() System.out.println(“Stringed”); public class Brass extends Instrument public void play() System.out.println(“Brass”); , public class Music public static void tune(Wind i) i.play();public static void tune(Stringed i) i.play();public s
7、tatic void tune(Brass i) i.play();,向上转型, public static void main(String args) Wind flute = new Wind(); tune(flute); / 长笛Stringed violin = new Stringed(); tune(violin); / 小提琴Brass frenchHorn = new Brass(); tune(frenchHorn); / 法国号 / 均未采用向上转型,向上转型, 我们来分析这样做的缺点:1、程序变得繁琐而复杂 每增加一个新的子类,这些方法必须重新写一遍2、程序存在漏洞的
8、可能 如果我们忘记了重载某个方法,编译器不会提醒 下面我们采用向上转型来改写这段代码,向上转型,class Instrument public void play() / 演奏 System.out.println(“Instrument”);,向上转型, public class Wind extends Instrument / 音乐分为管乐、弦乐、鼓乐等等public void play()/ 覆盖基类继承下来的方法System.out.println(“wind”); ,向上转型, / 增加Stringed(弦乐)类和Brass(铜管乐)类public class Stringed e
9、xtends Instrument public void play()System.out.println(“Stringed”); public class Brass extends Instrument public void play()System.out.println(“Brass”); ,向上转型, public class Music public static void tune(Instrument i) i.play(); / 调音public static void main(String args) Wind flute = new Wind(); tune(fl
10、ute); / 长笛Stringed violin = new Stringed(); tune(violin); / 小提琴 ,向上转型, 归纳:多态的作用和优点 可扩展性 那些操纵基类接口的方法不需要任何改动就可以应用于新类 分析:增加一个新类时,我们需要做哪些工作?,多态, 归纳:多态的作用和优点 多态是将改变的事物与未改变的事物分离开来的重要技术 分析:我们对tune()方法所做的任何修改,不会对程序中其他不应受到影响的部分产生破坏,多态, i.play()调用的是父类的play方法还是子类的play方法? 更进一步,是哪个子类的play方法? 为什么正好是我们所期望的输出结果?编译器
11、是怎么知道的?,绑定,1、 绑定(Banding)的概念 将一个方法调用和一个方法主体关联起来的过程称为绑定,绑定,2、 绑定(Banding)的分类 静态绑定(前期绑定,编译时绑定) 在运行前进行绑定 动态绑定(后期绑定、运行时绑定) 在运行的时候根据对象的类型进行绑定,绑定,3、 例子分析 我们再来分析一个非常经典的例子,绑定, class Processor / 字符处理器 public void a() System.out.println(“Processor.a()”);public void b() System.out.println(“Processor.b()”);,绑定,
12、 class Upcase extends Processor public void b() / 实现对父类方法的覆盖 System.out.println(“Upcase.b()”);public void c() System.out.println(“Upcase.c()”);,绑定, class Processor / 字符处理器 public void a() System.out.println(“Processor.a()”);public void b() System.out.println(“Processor.b()”);,绑定, class Processor / 字
13、符处理器 public void a() System.out.println(“Processor.a()”);public void b() System.out.println(“Processor.b()”);,绑定, class Apply public static void main(String args) / 向上转型Processor pro = new Upcase();pro.a(); / 实际调用的是Processor.a() pro.b(); / 实际调用的是Upcase.b() pro.c(); / 编译错误 ,绑定, 向上转型的归纳:基父类 实例 = new 子
14、类(); 向上转型时,实例可以调用父类中所有特有的方法(未在子类中覆盖的方法) 实例可以调用子类中覆盖父类的方法(即多态) 但是不可以调用子类中特有的方法(即父类中没有的方法),绑定,4、 注意:只有普通的方法调用可以是动态绑定的,域与静态方法不存在动态绑定 任何域的解析都是在编译期间进行的 静态方法是类的方法(不存在与对象的绑定),构造器和多态,1、 构造器的调用顺序(复习) 构造器的调用原则 调用基类构造器,这个步骤会不断反复递归下去,构造器和多态, 递归的确切含义如下: 首先是不断上溯,一直到这个层次的根 调用基类构造器 然后调转方向不断下溯 首先是下一层的派生类,然后是再下一层的派生类
15、,一直到最底层的派生类,构造器和多态, 为什么首先调用基类的构造器? 只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。 保证在进入导出类的构造器时,在基类可供我们访问的成员都已得到初始化,构造器和多态, 初始化的基本原则 在下溯并调用派生类构造器的过程中,要遵守初始化的基本原则 包括第一基本原则和第二基本原则,构造器和多态, 典例分析:class Meal / 一餐(粗粮) Meal()System.out.println(“Meal()”);class Bread / 面包 Bread() System.out.println(“Bread()”);class Cheese
16、/ 奶酪 Cheese() System.out.println(Chesse();,构造器和多态, class Lettuce / 莴苣 Lettuce()System.out.println(“Lettuce()”);class Lunch extends Meal / 午餐 Lunch() System.out.println(“Lunch()”);class Portablelunch extends Lunch / 便携式 Portablelunch()System.out.println(Portablelunch);,构造器和多态, public class Sandwich extends Portablelunch private Bread b=new Bread();private Cheese c=new Cheese();private Lettuce l=new Lettuce();Sandwich() System.out.println(“Sandwich”);;public static void main(String args) new Sandwich();,