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页共30页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 第 1 页 共 30 页 致读者: 我从 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 日
6:复用类 Java最令人心动的特性就是它的代码复用了。但是仅仅拷贝源代码再作 修改是不能被称为“革命”的 那是C之类的过程语言所采用的办法,而且也不怎么成功。就像Java里 的一切,要解决这个问题还要靠类。你可以利用别人写好的、已经测试通 过的类来创建新的类,不必一切都从零开始 这么做的诀窍就是,要在不改动原有代码的前提下使用类。本章会介绍两 种做法。第一种非常简单:在新的类里直接创建旧的类的对象。这被称为 合成( compostion),因为新的类是由旧的类合成而来的。你所复用的只 是代码的功能,而不是它的形式。 第二种方法更为精妙。它会创建一个新的,与原来那个类同属一种类型的 类。你全盘接受了旧类的形式,在没有对它做修改的情况下往里面添加了 新的代码。这种神奇的做法就被称为继承( inheritance)。编译器会承担 绝大部分的工作。继承是面向对象编程的基石,它还有一些额外的含义, 对此我们会在第7章再做探讨 合成与继承在语法和行为上有许多相似之处(这很好理解,因为它们都是 在原有类的基础上创建新类)。你会在本章学到这些代码复用的机制 合成所使用的语法 实际上我们已经看到很多合成的案例了。只要把对象的 reference直接 放到新的类里面就行了。假设,你要创建一个新的类,其中有几个 String对象,几个 primitiⅳve数据,以及一个别的什么类型的对象。对 于非 primitive的对象,你只要把它的 reference放到类里就行了,但是 对于 primitive,你就只能直接定义了 //: c06: Sprinklersystem java // Composition for code reuse import com. bruceeckel. simpletest. class WaterSource private string s WaterSource i System. out. println("WaterSource()")i w String( "Constructed")i public string tostring()( return s public class Sprinklersystem t private static Test monitor new Test()i private string valvel, valve2, valve, valve 第2页共30页 www.wgqqh.com/shhgs/tij.html emailshhgs(@sohu.com
Chapter 6: Reusing Classes www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 第 2 页 共 30 页 6: 复用类 Java 最令人心动的特性就是它的代码复用了。但是仅仅拷贝源代码再作 修改是不能被称为“革命”的。 那是 C 之类的过程语言所采用的办法,而且也不怎么成功。就像 Java 里 的一切,要解决这个问题还要靠类。你可以利用别人写好的、已经测试通 过的类来创建新的类,不必一切都从零开始。 这么做的诀窍就是,要在不改动原有代码的前提下使用类。本章会介绍两 种做法。第一种非常简单:在新的类里直接创建旧的类的对象。这被称为 合成(compostion),因为新的类是由旧的类合成而来的。你所复用的只 是代码的功能,而不是它的形式。 第二种方法更为精妙。它会创建一个新的,与原来那个类同属一种类型的 类。你全盘接受了旧类的形式,在没有对它做修改的情况下往里面添加了 新的代码。这种神奇的做法就被称为继承(inheritance)。编译器会承担 绝大部分的工作。继承是面向对象编程的基石,它还有一些额外的含义, 对此我们会在第 7 章再做探讨。 合成与继承在语法和行为上有许多相似之处(这很好理解,因为它们都是 在原有类的基础上创建新类)。你会在本章学到这些代码复用的机制。 合成所使用的语法 实际上我们已经看到很多合成的案例了。只要把对象的 reference 直接 放到新的类里面就行了。假设,你要创建一个新的类,其中有几个 String 对象,几个 primitive 数据,以及一个别的什么类型的对象。对 于非 primitive 的对象,你只要把它的 reference 放到类里就行了,但是 对于 primitive,你就只能直接定义了: //: c06:SprinklerSystem.java // Composition for code reuse. import com.bruceeckel.simpletest.*; class WaterSource { private String s; WaterSource() { System.out.println("WaterSource()"); s = new String("Constructed"); } public String toString() { return s; } } public class SprinklerSystem { private static Test monitor = new Test(); private String valve1, valve2, valve3, valve4;
Thinking in Java 3Edition private WaterSource source; pr⊥ vate int i public String tostring()( return "valve =" valve +"\n "valve2 ="+ valve2 "\n" Ive "valve ="+ valve +"\n" i i+"\ source =t sourcei public static void main(String[] args) f SprinklerSystem sprinklers =new System. out. println(sprinklers) monitor. expect (new string[] i valve= null valve2=nullr valves= null valve null" }) /// 这两个类都定义了一个特殊的方法: tostring()。以后你就会知道,所 有非 primitive对象都有一个 tostring〔)方法,当编译器需要一个 String而它却是一个对象的时候,编译器就会自动调用这个方法。所以 当编译器从 Sprin klersystem tostring()的 sour ce t source 中看到,你想把 String同 WaterSource相加的时候,它就会说“由 于 String只能同 String相加,因此我要调用 source的 tostring(),因为只有这样才能把它转换成 String!”。于是它就把 这两个 String连起来,然后再 String的形式把结果返还给 System. out println()。如果你想让你写的类也具备这个功能,只要 写一个 tostring()方法就行了。 我们已经在第2章讲过,当 primitive数据作为类的成员的时候,会被自 动地初始化为零。而对象的 reference则会被初始化为nu,如果这 时,你去调用这个对象的方法,就会得到异常。能把它打印出来而不抛出 异常,这真是太好了(而且也很实用)。 第3页共30页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 第 3 页 共 30 页 private WaterSource source; private int i; private float f; public String toString() { return "valve1 = " + valve1 + "\n" + "valve2 = " + valve2 + "\n" + "valve3 = " + valve3 + "\n" + "valve4 = " + valve4 + "\n" + "i = " + i + "\n" + "f = " + f + "\n" + "source = " + source; } public static void main(String[] args) { SprinklerSystem sprinklers = new SprinklerSystem(); System.out.println(sprinklers); monitor.expect(new String[] { "valve1 = null", "valve2 = null", "valve3 = null", "valve4 = null", "i = 0", "f = 0.0", "source = null" }); } } ///:~ 这两个类都定义了一个特殊的方法:toString( )。以后你就会知道,所 有非 primitive 对象都有一个 toString( )方法,当编译器需要一个 String 而它却是一个对象的时候,编译器就会自动调用这个方法。所以 当编译器从 SprinklerSystem.toString( )的: "source = " + source; 中看到,你想把 String 同 WaterSouce 相加的时候,它就会说“由 于 String 只能同 String 相加,因此我要调用 source 的 toString( ),因为只有这样才能把它转换成 String!”。 于是它就把 这两个 String 连起来,然后再 String 的形式把结果返还给 System.out.println( )。如果你想让你写的类也具备这个功能,只要 写一个 toString( )方法就行了。 我们已经在第 2 章讲过,当 primitive 数据作为类的成员的时候,会被自 动地初始化为零。而对象的 reference 则会被初始化为 null,如果这 时,你去调用这个对象的方法,就会得到异常。能把它打印出来而不抛出 异常,这真是太好了(而且也很实用)
“编译器不为 reference准备默认对象”的这种做法,实际上也是很合 乎逻辑的。因为在很多情况下,这么做会引发不必要的性能开销。如果你 想对 reference进行初始化,那么可以在以下几个时间进行: 1.在定义对象的时候。这就意味着在构造函数调用之前,它们已经初始化 完毕了。 2.在这个类的构造函数里。 3.在即将使用那个对象之前。这种做法通常被称为“偷懒初始化( (lazy initialization)”。如果碰到创建对象的代价很高,或者不是每次都需要 创建对象的时候,这种做法就能降低程序的开销了。 下面这段程序把这三种办法都演示一遍 //: c06: Bath. j // Constructor initialization with composition import com. bruceeckel. simpletest* Syst s new String("Constructed")i pub g tostring ()t return si public class Bath i private static Test monitor new Test()i private String / Initializing at point of definition s1 s3,s4; private Soap castille private int l public Bath()t System. out. println("Inside Bath()")i 3= new string( Joy toy =3.14f public String tostring()( null)// De layed return "s1="+s1+"\n"+ 2 castille =w+ castille public static void main(String[] args)i 第4页共30页 www.wgqqh.com/shhgs/tij.html emailshhgs(@sohu.com
Chapter 6: Reusing Classes www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 第 4 页 共 30 页 “编译器不为 reference 准备默认对象”的这种做法,实际上也是很合 乎逻辑的。因为在很多情况下,这么做会引发不必要的性能开销。如果你 想对 reference 进行初始化,那么可以在以下几个时间进行: 1. 在定义对象的时候。这就意味着在构造函数调用之前,它们已经初始化 完毕了。 2. 在这个类的构造函数里。 3. 在即将使用那个对象之前。这种做法通常被称为“偷懒初始化(lazy initialization)”。如果碰到创建对象的代价很高,或者不是每次都需要 创建对象的时候,这种做法就能降低程序的开销了。 下面这段程序把这三种办法都演示一遍: //: c06:Bath.java // Constructor initialization with composition. import com.bruceeckel.simpletest.*; class Soap { private String s; Soap() { System.out.println("Soap()"); s = new String("Constructed"); } public String toString() { return s; } } public class Bath { private static Test monitor = new Test(); private String // Initializing at point of definition: s1 = new String("Happy"), s2 = "Happy", s3, s4; private Soap castille; private int i; private float toy; public Bath() { System.out.println("Inside Bath()"); s3 = new String("Joy"); i = 47; toy = 3.14f; castille = new Soap(); } public String toString() { if(s4 == null) // Delayed initialization: s4 = new String("Joy"); return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "toy = " + toy + "\n" + "castille = " + castille; } public static void main(String[] args) { Bath b = new Bath();
Thinking in Java 3Edition System. out. println (b) monitor. expect(new String[] t Inside Bath( s1 Happy", 234 Hapy toy=3.14" castillo Constructed" }///:~ 注意,Bath的构造函数会先打印一条消息再进行初始化。如果你不在定 义对象的时候进行初始化,那么没人可以担保,在向这个对象的 reference发送消息的时候,它已经被初始化了—反倒是会有异常来告 诉你,它还没有初始化,。 调用 tostring()的时候它会先为s4赋一个值,这样它就不会未经初始 化而被使用了 继承所使用的语法 继承是Java(也是所有OOP语言)不可分割的一部分。实际上当你创建类 的时候,你就是在继承,要么是显式地继承别的什么类,要么是隐含地继 承了标准Java根类, Object 合成的语法很平淡,但继承就有所不同了。继承的时候,你得先声明“新 类和旧类是一样的。”跟平常一样,你得先在程序里写上类的名字,但是 在开始定义类之前,你还得加上 extends关键词和基类( base class)的 名字。做完这些之后,新类就会自动获得基类的全部成员和方法。下面就 是一个例子 //: c06: Detergent. java // Inheritance syntax properties import com. bruceeckel. simpletest class Cleanser protected static Test monitor new Test ()i private string s= new String(" Cleanser public void append(string a) s+= ai public void dilute() append(" dilute ())i) public void apply()( append(" apply()") public void scrub()( append(" scrub()") public string tostring()t return s; public static void main (String[ args)i Cleanser x new Cleanser xdilute()x apply ();x scrub()i 第5页共30页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 第 5 页 共 30 页 System.out.println(b); monitor.expect(new String[] { "Inside Bath()", "Soap()", "s1 = Happy", "s2 = Happy", "s3 = Joy", "s4 = Joy", "i = 47", "toy = 3.14", "castille = Constructed" }); } } ///:~ 注意,Bath 的构造函数会先打印一条消息再进行初始化。如果你不在定 义对象的时候进行初始化,那么没人可以担保,在向这个对象的 reference 发送消息的时候,它已经被初始化了——反倒是会有异常来告 诉你,它还没有初始化,。 调用 toString( )的时候它会先为 s4 赋一个值,这样它就不会未经初始 化而被使用了。 继承所使用的语法 继承是 Java(也是所有 OOP 语言)不可分割的一部分。实际上当你创建类 的时候,你就是在继承,要么是显式地继承别的什么类,要么是隐含地继 承了标准 Java 根类,Object。 合成的语法很平淡,但继承就有所不同了。继承的时候,你得先声明“新 类和旧类是一样的。”跟平常一样,你得先在程序里写上类的名字,但是 在开始定义类之前,你还得加上 extends 关键词和基类(base class)的 名字。做完这些之后,新类就会自动获得基类的全部成员和方法。下面就 是一个例子: //: c06:Detergent.java // Inheritance syntax & properties. import com.bruceeckel.simpletest.*; class Cleanser { protected static Test monitor = new Test(); private String s = new String("Cleanser"); public void append(String a) { s += a; } public void dilute() { append(" dilute()"); } public void apply() { append(" apply()"); } public void scrub() { append(" scrub()"); } public String toString() { return s; } public static void main(String[] args) { Cleanser x = new Cleanser(); x.dilute(); x.apply(); x.scrub();