JAVA 之 第7章 多形性

原创 2001年07月26日 13:20:00

第7章 多形性

“对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承。”

“多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长”。
通过合并各种特征与行为,封装技术可创建出新的数据类型。通过对具体实施细节的隐藏,可将接口与实施细节分离,使所有细节成为“private”(私有)。这种组织方式使那些有程序化编程背景人感觉颇为舒适。但多形性却涉及对“类型”的分解。通过上一章的学习,大家已知道通过继承可将一个对象当作它自己的类型或者它自己的基础类型对待。这种能力是十分重要的,因为多个类型(从相同的基础类型中衍生出来)可被当作同一种类型对待。而且只需一段代码,即可对所有不同的类型进行同样的处理。利用具有多形性的方法调用,一种类型可将自己与另一种相似的类型区分开,只要它们都是从相同的基础类型中衍生出来的。这种区分是通过各种方法在行为上的差异实现的,可通过基础类实现对那些方法的调用。
在这一章中,大家要由浅入深地学习有关多形性的问题(也叫作动态绑定、推迟绑定或者运行期绑定)。同时举一些简单的例子,其中所有无关的部分都已剥除,只保留与多形性有关的代码。

7.1 上溯造型
在第6章,大家已知道可将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型”——因为继承树的画法是基础类位于最上方。
但这样做也会遇到一个问题,如下例所示(若执行这个程序遇到麻烦,请参考第3章的3.1.2小节“赋值”):


 c07;

 Note {
    value;
   Note( val) { value = val; }
     Note
    middleC =  Note(0), 
    cSharp =  Note(1),
    cFlat =  Note(2);
} 

 Instrument {
    play(Note n) {
    System.out.println();
  }
}



 Wind  Instrument {
  
    play(Note n) {
    System.out.println();
  }
}

  Music {
     tune(Instrument i) {
    
    i.play(Note.middleC);
  }
     main(String[] args) {
    Wind flute =  Wind();
    tune(flute); 
  }
} 

其中,方法Music.tune()接收一个Instrument句柄,同时也接收从Instrument衍生出来的所有东西。当一个Wind句柄传递给tune()的时候,就会出现这种情况。此时没有造型的必要。这样做是可以接受的;Instrument里的接口必须存在于Wind中,因为Wind是从Instrument里继承得到的。从Wind向Instrument的上溯造型可能“缩小”那个接口,但不可能把它变得比Instrument的完整接口还要小。

7.1.1 为什么要上溯造型
这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢?进行上溯造型时,就可能产生这方面的疑惑。而且如果让tune()简单地取得一个Wind句柄,将其作为自己的自变量使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内Instrument的每种类型写一个全新的tune()。假设按照前面的推论,加入Stringed(弦乐)和Brass(铜管)这两种Instrument(乐器):


 Note2 {
    value;
   Note2( val) { value = val; }
     Note2
    middleC =  Note2(0), 
    cSharp =  Note2(1),
    cFlat =  Note2(2);
} 

 Instrument2 {
    play(Note2 n) {
    System.out.println();
  }
}

 Wind2  Instrument2 {
    play(Note2 n) {
    System.out.println();
  }
}

 Stringed2  Instrument2 {
    play(Note2 n) {
    System.out.println();
  }
}

 Brass2  Instrument2 {
    play(Note2 n) {
    System.out.println();
  }
}

  Music2 {
     tune(Wind2 i) {
    i.play(Note2.middleC);
  }
     tune(Stringed2 i) {
    i.play(Note2.middleC);
  }
     tune(Brass2 i) {
    i.play(Note2.middleC);
  }
     main(String[] args) {
    Wind2 flute =  Wind2();
    Stringed2 violin =  Stringed2();
    Brass2 frenchHorn =  Brass2();
    tune(flute); 
    tune(violin);
    tune(frenchHorn);
  }
} 

这样做当然行得通,但却存在一个极大的弊端:必须为每种新增的Instrument2类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后,假如想添加一个象tune()那样的新方法或者为Instrument添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行过载设置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。
但假如只写一个方法,将基础类作为自变量或参数使用,而不是使用那些特定的衍生类,岂不是会简单得多?也就是说,如果我们能不顾衍生类,只让自己的代码与基础类打交道,那么省下的工作量将是难以估计的。
这正是“多形性”大显身手的地方。然而,大多数程序员(特别是有程序化编程背景的)对于多形性的工作原理仍然显得有些生疏。

7.2 深入理解
对于Music.java的困难性,可通过运行程序加以体会。输出是Wind.play()。这当然是我们希望的输出,但它看起来似乎并不愿按我们的希望行事。请观察一下tune()方法:

public static void tune(Instrument i) {
// ...
i.play(Note.middleC);
}

它接收Instrument句柄。所以在这种情况下,编译器怎样才能知道Instrument句柄指向的是一个Wind,而不是一个Brass或Stringed呢?编译器无从得知。为了深入了理解这个问题,我们有必要探讨一下“绑定”这个主题。

7.2.1 方法调用的绑定
将一个方法调用同一个方法主体连接到一起就称为“绑定”(Binding)。若在程序运行以前执行绑定(由编译器和链接程序,如果有的话),就叫作“早期绑定”。大家以前或许从未听说过这个术语,因为它在任何程序化语言里都是不可能的。C编译器只有一种方法调用,那就是“早期绑定”。
上述程序最令人迷惑不解的地方全与早期绑定有关,因为在只有一个Instrument句柄的前提下,编译器不知道具体该调用哪个方法。
解决的方法就是“后期绑定”,它意味着绑定在运行期间进行,以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。
Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。
为什么要把一个方法声明成final呢?正如上一章指出的那样,它能防止其他人覆盖那个方法。但也许更重要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可为final方法调用生成效率更高的代码。

7.2.2 产生正确的行为
知道Java里绑定的所有方法都通过后期绑定具有多形性以后,就可以相应地编写自己的代码,令其与基础类沟通。此时,所有的衍生类都保证能用相同的代码正常地工作。或者换用另一种方法,我们可以“将一条消息发给一个对象,让对象自行判断要做什么事情。”
在面向对象的程序设计中,有一个经典的“形状”例子。由于它很容易用可视化的形式表现出来,所以经常都用它说明问题。但很不幸的是,它可能误导初学者认为OOP只是为图形化编程设计的,这种认识当然是错误的。

JAVA编程思想:第7章 多形性

第7章 多形性“对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承。”“多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么...
  • cyp0207
  • cyp0207
  • 2007年09月24日 14:17
  • 391

java编程思想(第四版)_第7章 多形性

-------------------------------------------------本教程由yyc,spirit整理-----------------------------------...
  • yuyu622
  • yuyu622
  • 2007年10月08日 13:42
  • 728

Thinking in Java 第7章 多形性 总结

1.关于java的特性。 本书中提到的:数据抽象,继承,多形性(多态性) 普遍说法是:封装,继承,多态性 也有说四大特性的:抽象,封装,继承,多态性 归纳的不同,但意思是一样的。 2.上一章中对上溯造...
  • aaaaaaadsfsdgsdfh
  • aaaaaaadsfsdgsdfh
  • 2016年08月23日 16:20
  • 632

Java - Thinking in Java 第7章 习题

1/** * 惰性初始化 * * Created by wang on 15/8/6. */ class Init { public Init() { System....
  • u012515223
  • u012515223
  • 2015年08月20日 07:14
  • 2174

[深入理解Java虚拟机]第七章 类加载的过程

接下来我们详细讲解一下Java虚拟机中类加载的全过程,也就是加载、验证、准备、解析和初始化这5个阶段所执行的具体动作。加载“加载”是“类加载”(Class Loading)过程的一个阶段,希望读者没有...
  • qilixiang012
  • qilixiang012
  • 2015年10月24日 19:19
  • 564

《java与模式》笔记(三) 里氏替换原则

 ξ 7.2 什么是里氏替代原则☆ 里氏替换原则由Barbara Liskov提出,它的严格表达是,如果对每一个类型为T1的对象o1,都由类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o...
  • plusir
  • plusir
  • 2006年08月19日 19:25
  • 2157

ACCPHTMLS1第七章上机练习1

无标题文档 li{ list-style-type:none; width:100px; float:left; } a{ background:url(../Desktop/forumMenu...
  • baidu_36074048
  • baidu_36074048
  • 2016年10月19日 14:58
  • 696

Java编程基础之多形性

“对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承。” “多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样...
  • u013189665
  • u013189665
  • 2014年03月17日 21:10
  • 1111

javascript语言精粹笔记 5-7章

继承基于类的语言中,继承有两个好处 1、代码重用。2、包括了一套类型系统的规范。 js是基于原型的弱类型语言,它可以模拟基于类的模式,也可以用其他模式。伪类js不让对象直接从其他对象继承,反而插入...
  • kofforever
  • kofforever
  • 2017年02月08日 11:48
  • 95

JAVA 多形性

“多形性” 意味着“不同的形式”。 在面对对象的程序设计中,我们有通用的接口以及外观的不同形式,。通常通过抽象以及继承来实现。多形性是一种不可独立应用的特性,只可与其他元素协同使用。...
  • a940659387
  • a940659387
  • 2016年03月02日 13:52
  • 595
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JAVA 之 第7章 多形性
举报原因:
原因补充:

(最多只允许输入30个字)