致读者:
我从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/shhgs/tij.html。
如果你觉得好,请给告诉我,你的鼓励是我工作的动力;如果你觉得不好,那就更应该告诉我了,我会参考你的意见作修改的。我希望能通过这种方法,译出一本配得上原著的书。
shhgs
2003年9月8日
java笔记总结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) {
}
public String toString() { return noteName; }
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
"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 (管乐器):
//: 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);
"Wind.play() Middle C",
"Stringed.play() Middle C",
"Brass.play() Middle C"
});
}
} ///:~
这种做法不是不可以,但是有个重大缺陷:每次加入新的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)”。如果语
言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。