网管联盟 | 网管论坛 | 网管u家 | 网管博客 | 网管软件 | 网管求职 | 小游戏 | 网管搜索 | 网管原创 | 网管聚合 | 网管读摘 | 网管焦点 | 世界素材 | 会员投稿 | 会员中心 
中国网管联盟
Windows Linux Cisco 网络技术 数据库 黑客攻防 DotNet Java PHP 认证 新闻资讯 服务器 存储资讯 网络设备 网管学堂 技术专题 焦点 网吧频道
 当前位置: > bitsCN.com > JAVA > 其它技术 > 理解java的多形性  

理解java的多形性

2004-05-08  作者:BitsCN整理  来源:中国网管联盟  点评 投稿 收藏


  “对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承。”
  
  “多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长”。
  通过合并各种特征与行为,封装技术可创建出新的数据类型。通过对具体实施细节的隐藏,可将接口与实施细节分离,使所有细节成为“private”(私有)。这种组织方式使那些有程序化编程背景人感觉颇为舒适。但多形性却涉及对“类型”的分解。通过上一章的学习,大家已知道通过继承可将一个对象当作它自己的类型或者它自己的基础类型对待。这种能力是十分重要的,因为多个类型(从相同的基础类型中衍生出来)可被当作同一种类型对待。而且只需一段代码,即可对所有不同的类型进行同样的处理。利用具有多形性的方法调用,一种类型可将自己与另一种相似的类型区分开,只要它们都是从相同的基础类型中衍生出来的。这种区分是通过各种方法在行为上的差异实现的,可通过基础类实现对那些方法的调用。
网管u家u.bitscn@com

  在这一章中,大家要由浅入深地学习有关多形性的问题(也叫作动态绑定、推迟绑定或者运行期绑定)。同时举一些简单的例子,其中所有无关的部分都已剥除,只保留与多形性有关的代码。
  
  7.1 上溯造型
  在第6章,大家已知道可将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型”——因为继承树的画法是基础类位于最上方。
  但这样做也会遇到一个问题,如下例所示(若执行这个程序遇到麻烦,请参考第3章的3.1.2小节“赋值”):
  
  
  //: Music.java
  // Inheritance & upcasting
  package c07;
  
  class Note {
  private int value;
  private Note(int val) { value = val; }
  public static final Note
  middleC = new Note(0),
  cSharp = new Note(1),
  cFlat = new Note(2);
  } // Etc.
  
  class Instrument {
  public void play(Note n) {
  System.out.println("Instrument.play()");
  }
  }
  
  // Wind objects are instruments 中国网管论坛bbs.bitsCN.com
  // because they have the same interface:
  class Wind extends Instrument {
  // Redefine interface method:
  public void play(Note n) {
  System.out.println("Wind.play()");
  }
  }
  
  public class Music {
  public static void tune(Instrument i) {
  // ...
  i.play(Note.middleC);
  }
  public static void main(String[] args) {
  Wind flute = new Wind();
  tune(flute); // Upcasting
  }
  } ///:~
  
  其中,方法Music.tune()接收一个Instrument句柄,同时也接收从Instrument衍生出来的所有东西。当一个Wind句柄传递给tune()的时候,就会出现这种情况。此时没有造型的必要。这样做是可以接受的;Instrument里的接口必须存在于Wind中,因为Wind是从Instrument里继承得到的。从Wind向Instrument的上溯造型可能“缩小”那个接口,但不可能把它变得比Instrument的完整接口还要小。
  
  7.1.1 为什么要上溯造型
  这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢?进行上溯造型时,就可能产生这方面的疑惑。而且如果让tune()简单地取得一个Wind句柄,将其作为自己的自变量使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内Instrument的每种类型写一个全新的tune()。假设按照前面的推论,加入Stringed(弦乐)和Brass(铜管)这两种Instrument(乐器): 网管bitscn_com
  
  //: Music2.java
  // Overloading instead of upcasting
  
  class Note2 {
  private int value;
  private Note2(int val) { value = val; }
  public static final Note2
  middleC = new Note2(0),
  cSharp = new Note2(1),
  cFlat = new Note2(2);
  } // Etc.
  
  class Instrument2 {
  public void play(Note2 n) {
  System.out.println("Instrument2.play()");
  }
  }
  
  class Wind2 extends Instrument2 {
  public void play(Note2 n) {
  System.out.println("Wind2.play()");
  }
  }
  
  class Stringed2 extends Instrument2 {
  public void play(Note2 n) {
  System.out.println("Stringed2.play()");
  }
  }
  
  class Brass2 extends Instrument2 {
  public void play(Note2 n) {
  System.out.println("Brass2.play()");
  }
  }
  
  public class Music2 {
  public static void tune(Wind2 i) {
  i.play(Note2.middleC);

中国网管联盟bitsCN.com


  }
  public static void tune(Stringed2 i) {
  i.play(Note2.middleC);
  }
  public static void tune(Brass2 i) {
  i.play(Note2.middleC);
  }
  public static void main(String[] args) {
  Wind2 flute = new Wind2();
  Stringed2 violin = new Stringed2();
  Brass2 frenchHorn = new Brass2();
  tune(flute); // No upcasting
  tune(violin);
  tune(frenchHorn);
  }
  } ///:~
  
  这样做当然行得通,但却存在一个极大的弊端:必须为每种新增的Instrument2类编写与类紧密相关的方法。这意味着第一次就要求多得多的编程量。以后,假如想添加一个象tune()那样的新方法或者为Instrument添加一个新类型,仍然需要进行大量编码工作。此外,即使忘记对自己的某个方法进行过载设置,编译器也不会提示任何错误。这样一来,类型的整个操作过程就显得极难管理,有失控的危险。
  但假如只写一个方法,将基础类作为自变量或参数使用,而不是使用那些特定的衍生类,岂不是会简单得多?也就是说,如果我们能不顾衍生类,只让自己的代码与基础类打交道,那么省下的工作量将是难以估计的。 网管网www_bitscn_com
  这正是“多形性”大显身手的地方。然而,大多数程序员(特别是有程序化编程背景的)对于多形性的工作原理仍然显得有些生疏。
  
  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句柄的前提下,编译器不知道具体该调用哪个方法。
网管u家u.bitsCN.com

  解决的方法就是“后期绑定”,它意味着绑定在运行期间进行,以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:它们都要在对象中安插某些特殊类型的信息。
  Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。
  为什么要把一个方法声明成final呢?正如上一章指出的那样,它能防止其他人覆盖那个方法。但也许更重要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可为final方法调用生成效率更高的代码。
  
  7.2.2 产生正确的行为
  知道Java里绑定的所有方法都通过后期绑定具有多形性以后,就可以相应地编写自己的代码,令其与基础类沟通。此时,所有的衍生类都保证能用相同的代码正常地工作。或者换用另一种方法,我们可以“将一条消息发给一个对象,让对象自行判断要做什么事情。”

网管论坛bbs_bitsCN_com


  在面向对象的程序设计中,有一个经典的“形状”例子。由于它很容易用可视化的形式表现出来,所以经常都用它说明问题。但很不幸的是,它可能误导初学者认为OOP只是为图形化编程设计的,这种认识当然是错误的。

TAGs理解   方法   绑定   类型   对象   Instrument   编译   自己    
 上一篇:Java中文相关技术   下一篇:使toString()的创建自动化
相关文章列表
理解java的多形性 评论:
loading.. 评论加载中…
评论:请自觉遵守互联网相关政策法规,评论不得超过250字。

验证码: 注册用户
本类热门排行:
1.JACOB配置方法及两个常见错误解决
2.用java api进行sort
3.Java中this、super用法简谈
4.Class文件详解 (2)
5.Apache目录服务器中存储Java对象
6.Java 操作二维数组实例
7.Lucene 索引文件格式的规格定义
8.英文打字练习
9.Java如何共享资源
10.editplus能够编译java嘛? 如何设置
最新推荐文章:
1.J2SE实现windows读取网卡的物理地址
2.使用Jetty和DWR创建伸缩性Comet程序
3.经验分享:我的20天项目经历
4.实现Java跨平台运行的十二个注意事项
5.成功的必要条件:开源思想推动创新
6.100行Java代码构建一个线程池
7.用Stripes做Java Web开发
8.j2me创意--Wap浏览器的源代码
9.使用Hashtable对字符串进行碰撞
10.定制 bugzilla 进行项目管理
网管论坛交流:
·不疯魔不成活
·令你大开眼界的真正标准化机房,已整理重
·为赈灾,女孩舍身拍“裸照”
·Windows Server 2003服务器群集创建和配
·exchange2k3全套官方资料
·双儿一周岁了。。。特殊的礼物来啦。。
·存储备份技术版块守则
·无盘技术交流区守则
·DOS命令基础大全之命令详解<作者吐血
·Windows XP 操作系统默认设置需要注意的