致读者:我从2002年7月开始翻译这本书,当时还是第二版。但是翻完前言和介绍部分后,chinapub就登出广告,说要出版侯捷的译本。于是我中止了翻译,等着侯先生的作品。我是第一时间买的这本书,但是我失望了。比起第一版,我终于能看懂这本书了,但是相比我的预期,它还是差一点。所以当BruceEckel在他的网站上公开本书的第三版的时候,我决定把它翻译出来。说说容易,做做难。一本1000多页的书不是那么容易翻的。期间我也曾打过退堂鼓,但最终还是全部翻译出来了。从今年的两月初起,到7月底,我几乎放弃了所有的业余时间,全身心地投入本书的翻译之中。应该说,这项工作的难度超出了我的想像。首先,读一本书和翻译一本书完全是两码事。英语与中文是两种不同的语言,用英语说得很畅的句子,翻成中文之后就完全破了相。有时我得花好几分钟,用中文重述一句我能用几秒钟读懂的句子。更何况作为读者,一两句话没搞懂,并不影响你理解整本书,但对译者来说,这就不一样了。其次,这是一本讲英语的人写给讲英语的人的书,所以同很多要照顾非英语读者的技术文档不同,它在用词,句式方面非常随意。英语读者会很欣赏这一点,但是对外国读者来说,这就是负担了。再有,BruceEckel这样的大牛人,写了1000多页,如果都让你读懂,他岂不是太没面子?所以,书里还有一些很有“禅意”的句子。比如那句著名的“Thegenesisofthecomputerrevolutionwasinamachine.Thegenesisofourprogramminglanguagesthustendstolooklikethatmachine.”我就一直没吃准该怎么翻译。我想大概没人能吃准,说不定Bruce要的就是这个效果。这是一本公认的名著,作者在技术上的造诣无可挑剔。而作为译者,我的编程能力差了很多。再加上上面讲的这些原因,使得我不得不格外的谨慎。当我重读初稿的时候,我发现需要修改的地方实在太多了。因此,我不能现在就公开全部译稿,我只能公开已经修改过的部分。不过这不是最终的版本,我还会继续修订的。本来,我准备到10月份,等我修改完前7章之后再公开。但是,我发现我又有点要放弃了,因此我决定给自己一点压力,现在就公开。以后,我将修改完一章就公开一章,请关注。如果你觉得好,请给告诉我,你的鼓励是我工作的动力;如果你觉得不好,那就更应该告诉我了,我会参考你的意见作修改的。我希望能通过这种方法,译出一本配得上原著的书。shhgs2003年9月8日Chapter6:ReusingClasses6:复用类Java最令人心动的特性就是它的代码复用了。但是仅仅拷贝源代码再作修改是不能被称为“革命”的。那是C之类的过程语言所采用的办法,而且也不怎么成功。就像Java里的一切,要解决这个问题还要靠类。你可以利用别人写好的、已经测试通过的类来创建新的类,不必一切都从零开始。这么做的诀窍就是,要在不改动原有代码的前提下使用类。本章会介绍两种做法。第一种非常简单:在新的类里直接创建旧的类的对象。这被称为合成(compostion),因为新的类是由旧的类合成而来的。你所复用的只是代码的功能,而不是它的形式。第二种方法更为精妙。它会创建一个新的,与原来那个类同属一种类型的类。你全盘接受了旧类的形式,在没有对它做修改的情况下往里面添加了新的代码。这种神奇的做法就被称为继承(inheritance)。编译器会承担绝大部分的工作。继承是面向对象编程的基石,它还有一些额外的含义,对此我们会在第7章再做探讨。合成与继承在语法和行为上有许多相似之处(这很好理解,因为它们都是在原有类的基础上创建新类)。你会在本章学到这些代码复用的机制。合成所使用的语法实际上我们已经看到很多合成的案例了。只要把对象的reference直接放到新的类里面就行了。假设,你要创建一个新的类,其中有几个String对象,几个primitive数据,以及一个别的什么类型的对象。对于非primitive的对象,你只要把它的reference放到类里就行了,但是对于primitive,你就只能直接定义了://:c06:SprinklerSystem.java//Compositionforcodereuse.importcom.bruceeckel.simpletest.*;classWaterSource{privateStrings;WaterSource(){System.out.println(WaterSource());s=newString(Constructed);}publicStringtoString(){returns;}}publicclassSprinklerSystem{privatestaticTestmonitor=newTest();privateStringvalve1,valve2,valve3,valve4;privateWaterSourcesource;privateinti;privatefloatf;publicStringtoString(){returnvalve1=+valve1+\n+valve2=+valve2+\n+valve3=+valve3+\n+valve4=+valve4+\n+i=+i+\n+f=+f+\n+source=+source;}publicstaticvoidmain(String[]args){SprinklerSystemsprinklers=newSprinklerSystem();System.out.println(sprinklers);monitor.expect(newString[]{valve1=null,valve2=null,valve3=null,valve4=null,i=0,f=0.0,source=null});}}///:~这两个类都定义了一个特殊的方法:toString()。以后你就会知道,所有非primitive对象都有一个toString()方法,当编译器需要一个String而它却是一个对象的时候,编译器就会自动调用这个方法?__酝_庖4。所以当编译器从SprinklerSystem.toString()的:source=+source;中看到,你想把String同WaterSouce相加的时候,它就会说“由于String只能同String相加,因此我要调用source的toString(),因为只有这样才能把它转换成String!”。于是它就把这两个String连起来,然后再String的形式把结果返还给System.out.println()。如果你想让你写的类也具备这个功能,只要写一个toString()方法就行了。我们已经在第2章讲过,当primitive数据作为类的成员的时候,会被自动地初始化为零。而对象的reference则会被初始化为null,如果这时,你去调用这个对象的方法,就会得到异常。能把它打印出来而不抛出异常,这真是太好了(而且也很实用)。Chapter6:ReusingClasses“编译器不为reference准备默认对象”的这种做法,实际上也是很合乎逻辑的。因为在很多情况下,这么做会引发不必要的性能开销。如果你想对reference进行初始化,那么可以在以下几个时间进行:1.在定义对象的时候。这就意味着在构造函数调用之前,它们已经初始化完毕了。2.在这个类的构造函数里。3.在即将使用那个对象之前。这种做法通常被称为“偷懒初始化(lazyinitialization)”。如果碰到创建对象的代价很高,或者不是每次都需要创建对象的时候,这种做法就能降低程序的开销了。下面这段程序把这三种办法都演示一遍://:c06:Bath.java//Constructorinitializationwithcomposition.importcom.bruceeckel.simpletest.*;classSoap{privateStrings;Soap(){System.out.println(Soap());s=newString(Constructed);}publicStringtoString(){returns;}}publicclassBath{privatestaticTestmonitor=newTest();privateString//Initializingatpointofdefinition:s1=newString(Happy),s2=Happy,s3,s4;privateSoapcastille;privateinti;privatefloattoy;publicBath(){System.out.println(InsideBath());s3=newString(Joy);i=47;toy=3.14f;castille=newSoap();}publicStringtoString(){if(s4==null)//Delayedinitialization:s4=newString(Joy);returns1=+s1+\n+s2=+s2+\n+s3=+s3+\n+s4=+s4+\n+i=+i+\n+toy=+toy+\n+castille=+castille;}publicstaticvoidmain(String[]args){Bathb=newBath();System.out.println(b);monitor.expect(newString[]{InsideBath(),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关键词和基类(baseclass)的名字。做完这些之后,新类就会自动获得基类的全部成员和方法。下面就是一个例子://:c06:Detergent.java//Inheritancesyntax&properties.importcom.bruceeckel.simpletest.*;classCleanser{protectedstaticTestmonitor=newTest();privateStrings=newString(Cleanser);publicvoidappend(Stringa){s+=a;}publicvoiddilute(){append(dilute());}publicvoidapply(){append(apply());}publicvoidscrub(){append(scrub());}publicStringtoString(){returns;}publicstaticvoidmain(String[]args){Cleanserx=newCleanser();x.dilute();x.apply();x.scrub();Chapter6:ReusingClassesSystem.out.println(x);monitor.expect(newString[]{Cleanserdilute()apply()scrub