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)”。如果语 言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其