Thinking in Java 3Edition 致读者: 我从2002年7月开始翻译这本书,当时还是第二版。但是翻完前言和介绍部分 后, chinapub就登出广告,说要出版侯捷的译本。于是我中止了翻译,等着侯 先生的作品。 我是第一时间买的这本书,但是我失望了。比起第一版,我终于能看懂这本书 了,但是相比我的预期,它还是差一点。所以当 Bruce Eckel在他的网站上公开 本书的第三版的时候,我决定把它翻译出来 说说容易,做做难。一本1000多页的书不是那么容易翻的。期间我也曾打过退 堂鼓,但最终还是全部翻译出来了。从今年的两月初起,到7月底,我几乎放 弃了所有的业余时间,全身心地投入本书的翻译之中。应该说,这项工作的难 度超出了我的想像。 首先,读一本书和翻译一本书完全是两码事。英语与中文是两种不同的语言, 用英语说得很畅的句子,翻成中文之后就完全破了相。有时我得花好几分钟 用中文重述一句我能用几秒钟读懂的句子。更何况作为读者,一两句话没搞 懂,并不影响你理解整本书,但对译者来说,这就不一样了。 其次,这是一本讲英语的人写给讲英语的人的书,所以同很多要照顾非英语读 者的技术文档不同,它在用词,句式方面非常随意。英语读者会很欣赏这 点,但是对外国读者来说,这就是负担了 再有, Bruce Eckel这样的大牛人,写了1000多页,如果都让你读懂,他岂不是 太没面子?所以,书里还有一些很有“禅意”的句子。比如那句著名的“The genesis of the computer revolution was in a machine. The genesis of our programming languages thus tends to look like that machine.”我就一直没吃准该怎 么翻译。我想大概没人能吃准,说不定 Bruce要的就是这个效果。 这是一本公认的名著,作者在技术上的造诣无可挑剔。而作为译者,我的编程 能力差了很多。再加上上面讲的这些原因,使得我不得不格外的谨慎。当我重 读初稿的时候,我发现需要修改的地方实在太多了。因此,我不能现在就公开 全部译稿,我只能公开已经修改过的部分。不过这不是最终的版本,我还会继 续修订的。 本来,我准备到10月份,等我修改完前7章之后再公开。但是,我发现我又有 点要放弃了,因此我决定给自己一点压力,现在就公开。以后,我将修改完 章就公开一章,请关注www.wgqgh.com/shhgs/tii.html 如果你觉得好,请给告诉我,你的鼓励是我工作的动力;如果你觉得不好,那 就更应该告诉我了,我会参考你的意见作修改的。我希望能通过这种方法,译 出一本配得上原著的书 2003年9月8日 第1页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 1 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 致读者: 我从 2002 年 7 月开始翻译这本书,当时还是第二版。但是翻完前言和介绍部分 后,chinapub 就登出广告,说要出版侯捷的译本。于是我中止了翻译,等着侯 先生的作品。 我是第一时间买的 这本书,但是我失望了。比起第一版,我终于能看懂这本书 了,但是相比我的预期,它还是差一点。所以当 Bruce Eckel 在他的网站上公开 本书的第三版的时候,我决定把它翻译出来。 说说容易,做做难。一本 1000 多页的书不是那么容易翻的。期间我也曾打过退 堂鼓,但最终还是全部翻译出来了。从今年的两月初起,到 7 月底,我几乎放 弃了所有的业余时间,全身心地投入本书的翻译之中。应该说,这项工作的难 度超出了我的想像。 首先,读一本书和翻译一本书完全是两码事。英语与中文是两种不同的语言, 用英语说得很畅的句子,翻成中文之后就完全破了相。有时我得花好几分钟, 用中文重述一句我能用几秒钟读懂的句子。更何况作为读者,一两句话没搞 懂,并不影响你理解整本书,但对译者来说,这就不一样了。 其次,这是一本讲英语的人写给讲英语的人的书,所以同很多要照顾非英语读 者的技术文档不同,它在用词,句式方面非常随意。英语读者会很欣赏这一 点,但是对外国读者来说,这就是负担了。 再有,Bruce Eckel 这样的大牛人,写了 1000 多页,如果都让你读懂,他岂不是 太没面子?所以,书里还有一些很有“禅意”的句子。比如那句著名的“The genesis of the computer revolution was in a machine. The genesis of our programming languages thus tends to look like that machine.”我就一直没吃准该怎 么翻译。我想大概没人能吃准,说不定 Bruce 要的就是这个效果。 这是一本公认的名著,作者在技术上的造诣无可挑剔。而作为译者,我的编程 能力差了很多。再加上上面讲的这些原因,使得我不得不格外的谨慎。当我重 读初稿的时候,我发现需要修改的地方实在太多了。因此,我不能现在就公开 全部译稿,我只能公开已经修改过的部分。不过这不是最终的版本,我还会继 续修订的。 本来,我准备到 10 月份,等我修改完前 7 章之后再公开。但是,我发现我又有 点要放弃了,因此我决定给自己一点压力,现在就公开。以后,我将修改完一 章就公开一章,请关注 www.wgqqh.com/shhgs/tij.html。 如果你觉得好,请给告诉我,你的鼓励是我工作的动力;如果你觉得不好,那 就更应该告诉我了,我会参考你的意见作修改的。我希望能通过这种方法,译 出一本配得上原著的书。 shhgs 2003 年 9 月 8 日
7:多态性 多态性是继数据抽象和继承之后的,面向对象的编程语言的第三个基本特 性 它提供了另一个层面的接口与实现的分离,也就是说把做什么和怎么做分 开来。多态性不但能改善代码的结构,提高其可读性,而且能让你创建可 扩展的( extensin/e)程序。所谓“可扩展”是指,程序不仅在项目最初的 开发阶段能“成长”,而且还可以在需要添加新特性的时候“成长”。 封装”通过将数据的特征与行为结合在一起,创建了一种新的数据类 型。“隐藏实现”通过将细节设成 private,完成了接口与实现的分离。 之所以要采取这种比较呆板的顺序来讲解,是要照顾那些过程语言的程序 员们。但是,多态性是站在“类”的角度来处理这种逻辑上的分离的。在 上一章中,你看到了,“继承”是怎样允许你将对象当作它自己的,或者 它的基类的类型来处理的。这是一个很重要的功能,因为它能让你把多个 类(派生自同一个基类的)当作一个类来处理,这样一段代码就能作用于很 多不同的类型了。“多态方法调用( polymorphic method cal)”能让 类表现出各自所独有的特点,只要这些类都是从同一个基类里派生出来的 就行了。当你通过基类的 reference调用方法的时候,这些不同就会通 过行为表现出来。 本章会从基础开始,通过一些简单的,只涉及多态行为的程序,来讲解多 态性(也被称为动态绑定『 dynamic binding」、后绑定「/ate binding」或运行时绑定『run- time bingding」)。 再访上传( upcasting) 你已经在第6章看到,怎样把对象当作它自己的或是它的基类的对象来使 用。把对象的 reference当作基类的 reference来用,被称为上传 ( upcasting)。因为在继承关系图中,基类总是在上面的。 但是问题也来了。下面我们用乐器来举例。由于乐器要演奏Note(音 符),所以我们在 package里单独创建一个Note类: 77: c07: music: Note. java / Notes to play on musical instruments package c07 musici import com. bruceeckel simpletest ublic class Note private string noteName private Note(String noteName)i this. noteName noteName public String tostring ()t return noteNamei I 第2页共29页 www.wgqqh.com/shhgs/tij.html
Chapter 7: Polymorphism 第 2 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 7: 多态性 多态性是继数据抽象和继承之后的,面向对象的编程语言的第三个基本特 性。 它提供了另一个层面的接口与实现的分离,也就是说把做什么和怎么做分 开来。多态性不但能改善代码的结构,提高其可读性,而且能让你创建可 扩展的(extensible)程序。所谓“可扩展”是指,程序不仅在项目最初的 开发阶段能“成长”,而且还可以在需要添加新特性的时候“成长”。 “封装”通过将数据的特征与行为结合在一起,创建了一种新的数据类 型。“隐藏实现”通过将细节设成 private,完成了接口与实现的分离。 之所以要采取这种比较呆板的顺序来讲解,是要照顾那些过程语言的程序 员们。但是,多态性是站在“类”的角度来处理这种逻辑上的分离的。在 上一章中,你看到了,“继承”是怎样允许你将对象当作它自己的,或者 它的基类的类型来处理的。这是一个很重要的功能,因为它能让你把多个 类(派生自同一个基类的)当作一个类来处理,这样一段代码就能作用于很 多不同的类型了。“多态方法调用(polymorphic method call)”能让 类表现出各自所独有的特点,只要这些类都是从同一个基类里派生出来的 就行了。当你通过基类的 reference 调用方法的时候,这些不同就会通 过行为表现出来。 本章会从基础开始,通过一些简单的,只涉及多态行为的程序,来讲解多 态性(也被称为动态绑定『dynamic binding』、后绑定『late binding』或运行时绑定『run-time bingding』)。 再访上传(upcasting) 你已经在第 6 章看到,怎样把对象当作它自己的或是它的基类的对象来使 用。把对象的 reference 当作基类的 reference 来用,被称为上传 (upcasting)。因为在继承关系图中,基类总是在上面的。 但是问题也来了。下面我们用乐器来举例。由于乐器要演奏 Note(音 符),所以我们在 package 里单独创建一个 Note 类: //: c07:music:Note.java // Notes to play on musical instruments. package c07.music; import com.bruceeckel.simpletest.*; public class Note { private String noteName; private Note(String noteName) { this.noteName = noteName; } public String toString() { return noteName; }
Thinking in Java 3Edition public static final Note MIDDLE C= new Note("Middle C") C SHARP new Note(C Sharp"), B FLAT new Note("B Flat")i / 这是一个“枚举( enumeration)”类,它创建了几个固定对象以供选 择。你不能再创建其它对象了,因为构造函数是 private的。 在接下去的程序中,Wind作为一种乐器继承了 Instrument //: c07: music: Music. java // Inheritance upcasting package c07. music import com. bruceeckel simpletest*i ublic class Music private static Test monitor new Test( public static void tune(Instrument i) 1. play(Note. MIDDLE C)i public static void main(String[] args) i Wind flute new wind()i tune(flute);// Upcasting monitor.expect(new String[] t Wind. play ( Middle C" //: c07: music: Wind. java package c07.music // Wind objects are instruments // because they have the same interface public class Wind extends Instrument i / Redefine interface method public void play (Note n) System. out. println("Wind. play()"+ n)i }///:~ Music tune()需要一个 Instrument的 reference作参数,但是它 也可以接受任何由 Instrument派生出来的 reference。当main( 未经转换就把Wind的 reference传给tune()的时候,这一切就发生 了。这是完全可以可行的——因为wind继承了 Instrument,因此它 必须实现 Instrument的接口。从Wind上传到 Instrument的时 第3页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 3 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com public static final Note MIDDLE_C = new Note("Middle C"), C_SHARP = new Note("C Sharp"), B_FLAT = new Note("B Flat"); // Etc. } ///:~ 这是一个“枚举(enumeration)”类,它创建了几个固定对象以供选 择。你不能再创建其它对象了,因为构造函数是 private 的。 在接下去的程序中,Wind 作为一种乐器继承了 Instrument: //: c07:music:Music.java // Inheritance & upcasting. package c07.music; import com.bruceeckel.simpletest.*; public class Music { private static Test monitor = new Test(); public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); // Upcasting monitor.expect(new String[] { "Wind.play() Middle C" }); } } ///:~ //: c07:music:Wind.java package c07.music; // Wind objects are instruments // because they have the same interface: public class Wind extends Instrument { // Redefine interface method: public void play(Note n) { System.out.println("Wind.play() " + n); } } ///:~ Music.tune( )需要一个 Instrument 的 reference 作参数,但是它 也可以接受任何由 Instrument 派生出来的 reference。当 main( ) 未经转换就把 Wind 的 reference 传给 tune( )的时候,这一切就发生 了。这是完全可以可行的——因为 Wind 继承了 Instrument,因此它 必须实现 Instrument 的接口。从 Wind 上传到 Instrument 的时
候,接口可能会“变窄”,但是再小也不会比 Instrument的接口更 把对象的类型忘掉 可能你会觉得 Music. java有些奇怪。为什么会有人要故意“忘掉”对 象的类型呢?上传就是在做这件事情。但是,让tune()直接拿Wind 的 reference作参数好像更简单一些。但是这会有一个问题:如果采用 这种方法,你就得为系统里的每个 Instrument都写一个新的tune() 方法。假设我们顺着这个思路,再加一个 Stringed(弦乐器)和一个 Brass(管乐器): 77: c07: music: Music2. java / Overloading instead of upcasting package c07. music import com. bruceeckel simpletest. class stringed extends Instrument pub id play (Note n)t System. out. println("Stringed play()+ n)i class Brass extends Instrument public void play(note n) System. out public class Music2 t private static Test monitor new Test( public static void tune(Wind i)( 1. play (Note. MIDDLE C) public static void tune(Stringed i)i public static void tune(Brass i)i 1. play(Note. MIDDLE C)i public static void main(String[ args) i Wind flute new wind(i Stringed violin new stringed()i Brass frenchHorn new Brass ( tune(flute)i// No upcasting tune(violin)i tune(frenchHorn) monitor. expect(new string[] t "Stringed play () Middle C" Brass. play () Middle C }/// 第4页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Chapter 7: Polymorphism 第 4 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 候,接口可能会“变窄”,但是再小也不会比 Instrument 的接口更 小。 把对象的类型忘掉 可能你会觉得 Music.java 有些奇怪。为什么会有人要故意“忘掉”对 象的类型呢?上传就是在做这件事情。但是,让 tune( )直接拿 Wind 的 reference 作参数好像更简单一些。但是这会有一个问题:如果采用 这种方法,你就得为系统里的每个 Instrument 都写一个新的 tune( ) 方法。假设我们顺着这个思路,再加一个 Stringed (弦乐器)和一个 Brass (管乐器): //: c07:music:Music2.java // Overloading instead of upcasting. package c07.music; import com.bruceeckel.simpletest.*; class Stringed extends Instrument { public void play(Note n) { System.out.println("Stringed.play() " + n); } } class Brass extends Instrument { public void play(Note n) { System.out.println("Brass.play() " + n); } } public class Music2 { private static Test monitor = new Test(); public static void tune(Wind i) { i.play(Note.MIDDLE_C); } public static void tune(Stringed i) { i.play(Note.MIDDLE_C); } public static void tune(Brass i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); Stringed violin = new Stringed(); Brass frenchHorn = new Brass(); tune(flute); // No upcasting tune(violin); tune(frenchHorn); monitor.expect(new String[] { "Wind.play() Middle C", "Stringed.play() Middle C", "Brass.play() Middle C" }); } } ///:~
Thinking in Java 3Edition 这种做法不是不可以,但是有个重大缺陷:每次加入新的 Instrument 的时候,你都必须专门为这个类写一个方法。这就意味着,不但定义类的 时候要多写代码,而且添加tune()之类的方法,或者添加新的 Instrument的时候,还会多出很多事情。此外,如果你忘了重载某个 方法,编译器是不会报错的,于是类型处理工作就完全乱套了。 如果你可以写只一个用基类,而不是具体的派生类作参数的方法,那会不 会更好一些呢?也就是,如果你可以忘掉它们都是派生类,只写同基类打 交道的代码,那会不会更好呢 这就是多态性要解决的问题。然而绝大多数从过程语言转过来的程序员 们,在理解多态性的运行机制的时候,都会些问题。 问题的关键 Music. java让人觉得费解的地方就是,运行的时候,真正产生输出的是 Wind. play()。诚然,这正是我们所希望,但是它却没有告诉我们它为 什么要这样运行。看看tune()方法 public static void tune(Instrument i)t // 1. play(Note. MIDDLE C) 它接受一个 Instrument的 reference做参数。那么编译器怎么会知道 这个 Instrument的 reference就指向一个Wind,而不是 Brass或 Stringed呢?编译器不可能知道。为了能深入的理解这个问题,我们先 来看看什么是“绑定( binding)”。 方法调用的绑定 将方法的调用连到方法本身被称为“绑定( binding)”。当绑定发生在程 序运行之前时(如果有的话,就是由编译器或连接器负责)被称作“前绑定 early binding)”。可能你从没听说过这个术语,因为面向过程的语言 根本就没有这个概念。C的编译器只允许一种方法调用,那就是前绑定。 上述例程之所以令人费解都是源于前绑定,因为当编译器只有一个 Instrument的 reference的时候,它是不知道该连到哪个方法的。 解决方案就是“后绑定( late binding)”,它的意思是要在程序运行的时 候,根据对象的类型来决定该绑定哪个方法。后绑定也被称为“动态绑定 ( dynamic binding)”或“运行时绑定(run- time binding)”。如果语 言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其 第5页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 5 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 这种做法不是不可以,但是有个重大缺陷:每次加入新的 Instrument 的时候,你都必须专门为这个类写一个方法。这就意味着,不但定义类的 时候要多写代码,而且添加 tune( ) 之类的方法,或者添加新的 Instrument 的时候,还会多出很多事情。此外,如果你忘了重载某个 方法,编译器是不会报错的,于是类型处理工作就完全乱套了。 如果你可以写只一个用基类,而不是具体的派生类作参数的方法,那会不 会更好一些呢?也就是,如果你可以忘掉它们都是派生类,只写同基类打 交道的代码,那会不会更好呢? 这就是多态性要解决的问题。然而绝大多数从过程语言转过来的程序员 们,在理解多态性的运行机制的时候,都会些问题。 问题的关键 Music.java 让人觉得费解的地方就是,运行的时候,真正产生输出的是 Wind.play( )。诚然,这正是我们所希望,但是它却没有告诉我们它为 什么要这样运行。看看 tune( )方法: public static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } 它接受一个 Instrument 的 reference 做参数。那么编译器怎么会知道 这个 Instrument 的 reference 就指向一个 Wind,而不是 Brass 或 Stringed 呢?编译器不可能知道。为了能深入的理解这个问题,我们先 来看看什么是“绑定(binding)”。 方法调用的绑定 将方法的调用连到方法本身被称为“绑定(binding)”。当绑定发生在程 序运行之前时(如果有的话,就是由编译器或连接器负责)被称作“前绑定 (early binding)”。可能你从没听说过这个术语,因为面向过程的语言 根本就没有这个概念。C 的编译器只允许一种方法调用,那就是前绑定。 上述例程之所以令人费解都是源于前绑定,因为当编译器只有一个 Instrument 的 reference 的时候,它是不知道该连到哪个方法的。 解决方案就是“后绑定(late binding)” ,它的意思是要在程序运行的时 候,根据对象的类型来决定该绑定哪个方法。后绑定也被称为“动态绑定 (dynamic binding)”或“运行时绑定(run-time binding)”。如果语 言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其
合适的方法的机制。也就是说,编译器还是不知道对象的类型,但是方法 的调用机制会找出,并且调用正确的方法。后绑定机制会随语言的不同而 不同,但是你可以设想,对象里面必定存有“它属于哪种类型”的信息。 除了 static和fina方法( private方法隐含有 final的意思),Jav 的所有的方法都采用后绑定。也就是说,通常情况下你不必考虑是不是应 该采用后绑定——它是自动的 为什么要声明 final方法?我们在上一章指出,这样可以禁止别人覆写那 个方法。不过,更重要的可能还是要“关闭”它的动态绑定,或者更确切 的说,告诉编译器这里不需要使用后绑定。这样编译器就能为 final方法 生成稍微高效一些的调用代码。然而在绝大多数情况下,这种做法并不会 对程序的总体性能产生什么影响,因此最好还是只把fina当作一种设计 手段来用,而不要去考虑用它来提高性能。 产生正确的行为 旦知道Java通过后绑定实现了多态的方法调用,你就可以只编写同基 类打交道的代码了。因为你知道所有的派生类也能正确地使用这些代码。 或者换一个说法,你“向对象发一个消息,让它自己判断该做些什么。 形状”就是讲解OOP的一个经典的例子。它看起来直观,因此被广 泛使用,但是不幸的是,这会让新手误以为OoP只是用来处理图像编程 的,这显然不对。 在这个例子中,基类被称作 Shape,它有好几个派生类: Circle, Square, Triangle,等等。这个例子之所以好,是因为我们能很自然 地说“圆形是一种形状”,而听的人也能明白。下面的继承关系图体现了 这种关系 Shape nheritance draw erase Circle quare Triangle Cird draw) draw( eterenceerase() erase( erase 第6页共29页 www.wgqqh.com/shhgs/tij.html
Chapter 7: Polymorphism 第 6 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 合适的方法的机制。也就是说,编译器还是不知道对象的类型,但是方法 的调用机制会找出,并且调用正确的方法。后绑定机制会随语言的不同而 不同,但是你可以设想,对象里面必定存有“它属于哪种类型”的信息。 除了 static 和 final 方法(private 方法隐含有 final 的意思),Java 的所有的方法都采用后绑定。也就是说,通常情况下你不必考虑是不是应 该采用后绑定——它是自动的。 为什么要声明 final 方法?我们在上一章指出,这样可以禁止别人覆写那 个方法。不过,更重要的可能还是要“关闭”它的动态绑定,或者更确切 的说,告诉编译器这里不需要使用后绑定。这样编译器就能为 final 方法 生成稍微高效一些的调用代码。然而在绝大多数情况下,这种做法并不会 对程序的总体性能产生什么影响,因此最好还是只把 final 当作一种设计 手段来用,而不要去考虑用它来提高性能。 产生正确的行为 一旦知道 Java 通过后绑定实现了多态的方法调用,你就可以只编写同基 类打交道的代码了。因为你知道所有的派生类也能正确地使用这些代码。 或者换一个说法,你“向对象发一个消息,让它自己判断该做些什么。” “形状” 就是讲解 OOP 的一个经典的例子。它看起来直观,因此被广 泛使用,但是不幸的是,这会让新手误以为 OOP 只是用来处理图像编程 的,这显然不对。 在这个例子中,基类被称作 Shape,它有好几个派生类:Circle, Square,Triangle,等等。这个例子之所以好,是因为我们能很自然 地说“圆形是一种形状”,而听的人也能明白。下面的继承关系图体现了 这种关系:
下面这句就是在“上传”: Shape s new Circle() 这里先创建了一个 Circle对象,接着马上把它的 reference赋给了 Shape。看上去这像是一个错误(一种类型怎么能赋给另一种):但是由 于 Circle是由 Shape派生出来的, Circle就是一种 Shape,因此这 种做法非常正确。所以编译器会毫不含糊地接受这条语句,什么错都不 报 假设你调用了一个基类方法(派生类已经覆写这个方法) 可能你会认为,这次应该总调用 Shape的draw()了吧,因为毕竟这 是 Shape的 reference——编译器又怎么会知道还要做其它事情呢?但 是由于实现了后绑定(多态性),实际上它会调用 Circle. draw()。 下面的例程稍微作了一些变化: 7/: c07: Shapes. java Polymorphism in Java import com. bruceeckel simpletest* class Shape void draw()[ void erase(( class Circle extends Shape i void draw( System. out. println("Circle. draw()") oid erase i System. out. println("Circle erase()") class square extends Shape void draw([ System. out. println("Square. draw()")i void erased System. out. printin("Square erase()")i class Triangle extends shape void draw()[ 第7页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 7 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 下面这句就是在“上传”: Shape s = new Circle(); 这里先创建了一个 Circle 对象,接着马上把它的 reference 赋给了 Shape。看上去这像是一个错误(一种类型怎么能赋给另一种);但是由 于 Circle 是由 Shape 派生出来的,Circle 就是一种 Shape,因此这 种做法非常正确。所以编译器会毫不含糊地接受这条语句,什么错都不 报。 假设你调用了一个基类方法(派生类已经覆写这个方法): s.draw(); 可能你会认为,这次应该总调用 Shape 的 draw( )了吧,因为毕竟这 是 Shape 的 reference——编译器又怎么会知道还要做其它事情呢?但 是由于实现了后绑定(多态性),实际上它会调用 Circle.draw( )。 下面的例程稍微作了一些变化: //: c07:Shapes.java // Polymorphism in Java. import com.bruceeckel.simpletest.*; import java.util.*; class Shape { void draw() {} void erase() {} } class Circle extends Shape { void draw() { System.out.println("Circle.draw()"); } void erase() { System.out.println("Circle.erase()"); } } class Square extends Shape { void draw() { System.out.println("Square.draw()"); } void erase() { System.out.println("Square.erase()"); } } class Triangle extends Shape { void draw() {
System. out. println(" Triangle. draw()) void erase( System. out. println("Triangleerase()") /A " factory that randomly creates shapes class RandomShapeGenerator rivate Random rand new Random ()i public Shape next() switch(rand. nextInt(3))( default urn new circle() case l: return new Square () case 2: return new Triangle ()i public class Shapes t private static Test monitor new Test( private static Random Shape Generator gen ew Random Shape Generator ( public static void main(String[] args)i Shape[] s= new Shape [9] / Fill up the array with shapes: for (int 1=0; i <slength; i++) s[i]=gen. next() / Make polymorphic method calls: for(int i =0; i<slength; i++) monitor. expect(new object[] t new TestExpression ("89 (Circle Square I Triangle) +"\\ draw\\(\)", s length 基类 Shape为继承类定义了一个共用的接口—也就是说,所有的 Shape都有draw()和 erase()这两个方法。派生类会覆写这两个方 法,以提供各自所独有的行为。 Random Shape Generator是一个“工厂”,每次调用它的next() 方法的时候,它都会随机选取一个 Shape对象,然后返回这个对象的 reference。要注意,上传就发生在 return语句,它拿到的都是 Circle, Square或是 Triangle,而它返回的都是 Shape。因此每次 调用next()的时候,你都没法知道返回值的具体类型,因为传给你的永 远是普通的 Shape reference。 man()创建了一个 Shape reference的数组,然后用 Random Shape Generator. next()把它填满。这时,你只知道它们 第8页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Chapter 7: Polymorphism 第 8 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com System.out.println("Triangle.draw()"); } void erase() { System.out.println("Triangle.erase()"); } } // A "factory" that randomly creates shapes: class RandomShapeGenerator { private Random rand = new Random(); public Shape next() { switch(rand.nextInt(3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } } public class Shapes { private static Test monitor = new Test(); private static RandomShapeGenerator gen = new RandomShapeGenerator(); public static void main(String[] args) { Shape[] s = new Shape[9]; // Fill up the array with shapes: for(int i = 0; i < s.length; i++) s[i] = gen.next(); // Make polymorphic method calls: for(int i = 0; i < s.length; i++) s[i].draw(); monitor.expect(new Object[] { new TestExpression("%% (Circle|Square|Triangle)" + "\\.draw\\(\\)", s.length) }); } } ///:~ 基类 Shape 为继承类定义了一个共用的接口——也就是说,所有的 Shape 都有 draw( )和 erase( )这两个方法。派生类会覆写这两个方 法,以提供各自所独有的行为。 RandomShapeGenerator 是一个“工厂”,每次调用它的 next( ) 方法的时候,它都会随机选取一个 Shape 对象,然后返回这个对象的 reference。要注意,上传就发生在 return 语句,它拿到的都是 Circle,Square 或是 Triangle,而它返回的都是 Shape。因此每次 调用 next( )的时候,你都没法知道返回值的具体类型,因为传给你的永 远是普通的 Shape reference。 main( )创建了一个 Shape reference 的数组,然后用 RandomShapeGenerator.next( )把它填满。这时,你只知道它们
Thinking in Java 3 Editio 都是 Shape,至于更具体的,就不得而知了(编译器也一样)。但是一旦 程序运行,当你遍历数组,逐个地调用它们的draw()方法的时候,你 就会发现,draw()的行为魔法般地变成了各个具体类型的正确行为 之所以要随机选择形状,是想做得道地一点,让你相信编译器在编译的时 候也不知道该选用哪个方法。所有的draw()都必须使用后绑定。 可扩展性 现在,我们再回到乐器的例子。由于有了多态性,你就可以根据需要,往 系统里添加任意多个新类型,而不用担心还要修改tune()方法了。在 个设计良好的OOP程序中,绝大多数方法都会和tune()一样,只跟基 类接口打交道。这种程序是可扩展的,因为你可以通过“让新的数据类型 继承通用的基类”的方法,来添加新的功能。而那些与基类接口打交道的 方法,根本不需要作修改就能适应新的类。 就拿乐器为例,想想该怎样往基类里加新的方法,并且通过继承产生一些 新的类。下面就是关系图 Instrument void play o String what void adjust Wind Percussion Stringed void play o void playo void play o String what String what string whati〕 void adjust void adjust void adjust Woodwind Brass string what void adjust 第9页共29页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 9 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 都是 Shape,至于更具体的,就不得而知了(编译器也一样)。但是一旦 程序运行,当你遍历数组,逐个地调用它们的 draw( )方法的时候,你 就会发现, draw( )的行为魔法般地变成了各个具体类型的正确行为 了。 之所以要随机选择形状,是想做得道地一点,让你相信编译器在编译的时 候也不知道该选用哪个方法。所有的 draw( )都必须使用后绑定。 可扩展性 现在,我们再回到乐器的例子。由于有了多态性,你就可以根据需要,往 系统里添加任意多个新类型,而不用担心还要修改 tune( )方法了。在一 个设计良好的 OOP 程序中,绝大多数方法都会和 tune( )一样,只跟基 类接口打交道。这种程序是可扩展的,因为你可以通过“让新的数据类型 继承通用的基类”的方法,来添加新的功能。而那些与基类接口打交道的 方法,根本不需要作修改就能适应新的类。 就拿乐器为例,想想该怎样往基类里加新的方法,并且通过继承产生一些 新的类。下面就是关系图:
这些新的类都能同原先未作修改的tune(〕方法协同工作。即便是在 tune()保存在另一个文件,而 Instrument的接口已经加入了新方法 的情况下,它也能无须重新编译而正常工作。下面就是这个关系图的实 现 77: c07: music: Music3 java // An extensible program package c07 musics; import com. bruceeckel. simpletest* class Instrument void play(note n)( System. out. println ("Instrument. play()"+ n)i String what()f return Instrument void adjust()1 class Wind extends Instrument void play(note n)t System. out. println("Wind. play()"+ n) String what( return Wind"i j void adjust()( class Percussion extends Instrument i oid play (note n) System. out. println("Percussion. play ( String what()t return "Percussion"; void adjust()i class stringed extends Instrument void play (note n)i System. out. println("Stringed play ()"t n)i String what()i return "Stringed"; 1 void adjust()I class Brass extends Wind i void play(note n) System. out. println("Brass. play()'+ n) void adjust()f System. out. println("Brass. adjust()")i wind extends wind System. out. println("Woodwind. play()+ n)i String what( return Woodwind"i) 第10页共29页 www.wgqqh.com/shhgs/tij.html
Chapter 7: Polymorphism 第 10 页 共 29 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 这些新的类都能同原先未作修改的 tune( )方法协同工作。即便是在 tune( )保存在另一个文件,而 Instrument 的接口已经加入了新方法 的情况下,它也能无须重新编译而正常工作。下面就是这个关系图的实 现: //: c07:music3:Music3.java // An extensible program. package c07.music3; import com.bruceeckel.simpletest.*; import c07.music.Note; class Instrument { void play(Note n) { System.out.println("Instrument.play() " + n); } String what() { return "Instrument"; } void adjust() {} } class Wind extends Instrument { void play(Note n) { System.out.println("Wind.play() " + n); } String what() { return "Wind"; } void adjust() {} } class Percussion extends Instrument { void play(Note n) { System.out.println("Percussion.play() " + n); } String what() { return "Percussion"; } void adjust() {} } class Stringed extends Instrument { void play(Note n) { System.out.println("Stringed.play() " + n); } String what() { return "Stringed"; } void adjust() {} } class Brass extends Wind { void play(Note n) { System.out.println("Brass.play() " + n); } void adjust() { System.out.println("Brass.adjust()"); } } class Woodwind extends Wind { void play(Note n) { System.out.println("Woodwind.play() " + n); } String what() { return "Woodwind"; } }