致读者 我从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/shags/tii.htm 如果你觉得好,请给告诉我,你的鼓励是我工作的动力;如果你觉得不好,那 就更应该告诉我了,我会参考你的意见作修改的。我希望能通过这种方法,译 出一本配得上原著的书。 2003年9月8日 第1页共48页
shhgs@wgqqh.com 1 ✁ ✂ 48 ✁ 2002 7 chinapub !"#$%!&'( )*+,-.& /0(12 345( 67,893:);<= ,>8(?@AB3CDE Bruce Eckel FG(HIJK (L(MNOPA!Q $$RSTTU3 1000 VW(XYZRS(@4[\]^_ `ab:c!Q,d(ef9g 7 hijk l,Dm(no4cpqrst (u*vw$ xy1(U z{!,(|} ~/3 3 ce* eX( $(* uc,>mi * 3;i=(13e =X ¡¢£¤ ¥Q$ X3¦, §¨ 3 ©(ª«¬©(ª(DEV%®¯ (°± ²XAF³´µ¶¯·¸¹º»¼ 3 C¥½¾Q$ ¿À, ÁmBruce Eckel ¦(Âê«, 1000 VWÄÅÆÇ¡=GÈX ɶÊDEËm3ÌmÍιÏ(8ÄYÐÑ(ÍThe genesis of the computer revolution was in a machine. The genesis of our programming languages thus tends to look like that machine.Ï3ÒÓÔwÕ Z|ÂÖª;ÓÔ$XO Bruce %( רŠ3 KÙ(ÑÐ1F°±J(ÚÛÜÝÞßà1(áâ ;ãB,VÁäJJ¶©( ÌåæçXXè½(éê fë(Mìíî%ïð(rµñFÉV,æòX;íFK cëó;Kôõïð^(X^ Xb:( ºö ÷ïø( QÔùg 10 ú-ïð 7 ûuÁKìíüm C%kl,æòNO¬ý þ3CãíFKEïð3 ûK3û www.wgqqh.com/shhgs/tij.html ÄÅ¡¬#¡(ay1( ã ÄÅ¡XY vw#,º¡(¹ 1ïð(7;^ µ !3 JåÐ( shhgs 2003 9 8
3:控制程序流程 就像有感知力的生物那样,程序应该有能力操控它的世界,并且在执行过 程中作决定 Java让你用运算符( operator)来控制数据,用执行控制语句来作决定。 Java继承了C++,因此C和C++程序员会对它的语句和运算符感到很 亲切。不过Java也作了一些改进和简化。 如果你觉得这一章还是有点难,那么先去看看本书附送的多媒体CD ROM: Foundations for Java。它里边包括了讲课的录音,幻灯片,练 习以及答案。设计这个课程的目的就是要让你尽快地掌握学习Java所需 的基础知识。 使用Java运算符 运算符需要一到两个参数,并且会产生一个新的值。参数的使用方法与普 通方法的调用不同,但是结果是相同的。加(+),减(-),乘(*),除 (/),以及等号(=)的用法同其它编程语言完全相同。 运算符作用于操作数( operand),并且会产生一个值。此外,运算符还被 用于修改操作数的值。这被称为副作用( side effect)。我们经常看到的, 用运算符来修改操作数的操作就是利用了这种副作用,但是你应该记住, 这种做法所产生的值也是可以用的,它同那些没有副作用的运算符是一 样 几乎所有的运算符都只能作用于 primitive。但是‘= 以及 !=’是例外,它们可以作用于任何对象(由此也是对象方面一个很让人 头晕的问题。)此外, String类也支持‘+’和‘+=’。 优先级 当一个表达式包含多个运算符的时候,运算符的优先顺序会决定该怎样计 算这个表达式的值。Java有一套判断计算顺序的特殊规则。其中最简单 的一条就是“先乘除后加减”。程序员们经常会把其余的优先顺序都给忘 了,所以你应该使用括号来明确的指明计算的顺序。例如: a=x+y-2/2+z 同一个表达式,加上两组括号之后就会有截然不同的意思 a=x+(y-2)/(2+z); 第2页共48页
shhgs@wgqqh.com 2 ✁ ✂ 48 ✁ 3: }mã(0Y¦âvwm;ãA(F^ â*1NO Java Ç¡ (operator)Q!"#!Q1NO Java ö$, C++æò C C++â%º¥A( g &'X^ Java [1,3Ìð()* ÄÅ¡ 3ûmCUYZ/+<< ,-(V./ CD ROM 0Foundations for JavaAË123,©4(56789: ;E<=>?@ ×4â(A(%Ç¡BCrDEF; Java Dî (GHI Java î%3ge×"ºJ03×K(L"(çµM µ(NXOÅ>(ä(+)P(-)Q(*)R (/)E<-S(=)(§Aáâc> 1)1"(operan d )ºJ03×Lò½ T )ïð1"(L TUV1(side effect)Wõ·<g( Qïð1"(1X, V1¡vwYZ TDJ0(L[ÝE(AYÌmV1( 3 ¦ ijDm( Æó;1) p rim itive[=\[==\E< [! =\]½AWÝE1)^¥_(`ò[¥_µ¶3×Ǫ ab(cd)ò½Strin g e[fg[+\[+=\ 3×hi´2jV× (M (k/lºNOwÕ¦@ ×hi´(LJava m3mno@l(pqrs§*b)t (3uÍ/QRäPÏâ%Wõ·ºP§o(k/lƬv ,DE¡vwç3SQwx(yw@(l]Ä0 a = x + y - 2/2 + z; 3×hi´äJez3Suºm{|X(¹}0 a = x + (y - 2)/(2 + z);
赋值 赋值用的是≡运算符。它的意思是“算出等号右边的值(通常称为 rvalue),然后拷贝到等号的左边(通常称为/aue)”。 rvalue可以是任 何常量,变量或者是能产生值的表达式,而laue则只能是一个明确 的,有名字的变量( named variable)。(也就是说,必须要有一个物理空 间来存储这个值。)比如,你可以将常量赋给变量 但是你不能将值赋给常量——也就是说,常量是不能当Ⅳ value的。(你不 能说4=a) 给 primitive赋值还是比较简单的。由于 primitive持有的是实实在在的 值,而不是指向对象的 reference,因此为 primitive赋值的时候,你是 将内容从一个地方直接拷贝到另一个地方。假设a和b都是 primitive, 如果你说a=b,那么b的内容会被拷贝到a。如果接下来你又修改了 a,那么很显然b是不会受到影响的。对于程序员来说,在绝大多数情况 下,这正是他们所需要的 但是,给对象赋值的时候,情况就有所不同了。只要你想操控对象,你就 得通过 reference来进行操作,所以当你“在对象之间”进行赋值的时 候,实际上你是在拷贝它的 reference。也就是说,如果c和d都是对 象,而你说c〓d,结果就成了c和d都指向原先只有d指着的那个对 象了。下面的例程演示了这种行为 // c03: Assignment. java // Assignment with objects is a bit tricky import com. bruceeckel. simpletest. class Number public class Assignment i static Test monitor new Test ( public static void main(String[] args) t Number nl= new Number(i Number n2 new Number()i System. out. println("l: nl. i:#+ nl.i+ sy n1.i=2 第3页共48页
shhgs@wgqqh.com 3 ✁ ✂ 48 ✁ ~L( = A(¹}Í!-S1(L(·U rvalue)|g-S(1(·U lvalue)Ïrval ue ÝE^ · ;J0L(hi´àl val ue só;3×wx (mÑ((n am ed variab l e)([$%m3×¢ 4Q ×L)8Ä¡ÝE·~¬0 a = 4; ¡X;L~¬·[$·X; l val ue ((¡X ;$ 4 = a; ) ¬ p rim itive ~L8)t(`) p rim itive gm(ññFF( LàXy¥_( referen ceæò p rim itive ~L(M¡ R3×rµÒg3×rµ? a b Æ p rim itive ÄÅ¡$ a=b YZ b (RºTg aÄÅQ¡üïð, aYZ| b Xºg (¥)â%Q$FÂV" GWDî%( ¬¥_~L(MmDX,ó%¡|¥_¡ ^ referen ce Q(1DE ¡ÍF¥_u4Ï(~L( MñJ¡FA( referen ce[$ÄÅ c d Æ¥ _à¡$ c = dOÅ, c d Æyå/óm d y.(Y×¥ _,¶(]â, 0 //: c03:Assignment.java // Assignment with objects is a bit tricky. import com.bruceeckel.simpletest.*; class Number { int i; } public class Assignment { static Test monitor = new Test(); public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); n1.i = 9; n2.i = 47; System.out.println("1: n1.i: " + n1.i + ", n2.i: " + n2.i); n1 = n2; System.out.println("2: n1.i: " + n1.i + ", n2.i: " + n2.i); n1.i = 27;
System. out. println("3: nl.i: #+ nl.i+ n2.i:"+n2.i); String[] t "2:n1.i:47,n2.i:47 3:n1.i:27,n2.i:27 首先,提请大家注意一下新加的这一彳 import com. bruceeckel. simpletest *i 它引入了专门用于测试本书代码的“ simpletext”类库,我们会到第 15章再详细讲解这个类库。在 Assignment类的开始部分,有这么 static Test monitor new Test(i 它会创建一个名为 monitor的的Test类的实例,而Test又是 simpletext的一个类。在main()的最后,你还会看到 monitor. expect (new string[] i 1:n1.i:9,n2.i:47", 2:n1.i:47,n2 "3:n1.i:27,n2.i:27 }) 这是用 String数组表示的,程序的预期输出。程序运行的时候,不但会 把输出打印岀来,而且会拿它同数组作比较,以检验是不是正确。于是你 会发现,如果程序用到了 simpletext,那它肯定会调用显示程序输出的 expect()。这样你就能从源代码里看到正确的输出了 Number类很简单。main()创建了两个 Number类的实例(n1和 n2)。它们的i被分别赋上了不同的值,然后把n2赋给n1,再修改 n1。在很多别的编程语言里,你可以设想n1和n2自始至终都是两个 独立的对象。但在Java里面,由于是对 reference进行赋值,因此从 expect()语句可以看出,对n1的修改会影响到n2!这是因为,n1 和n2都是 reference,而这两个 reference又指向同一个对象。(原先 那个在n1里面的 reference,指向的是保存9这个值的对象。在赋值过 第4页共48页
shhgs@wgqqh.com 4 ✁ ✂ 48 ✁ System.out.println("3: n1.i: " + n1.i + ", n2.i: " + n2.i); monitor.expect(new String[] { "1: n1.i: 9, n2.i: 47", "2: n1.i: 47, n2.i: 47", "3: n1.i: 27, n2.i: 27" }); } } ///:~ ~/¹3Kä( 30 import com.bruceeckel.simpletest.*; At, )¡¢ £(ÍsimpletestÏe¤Wºg 15 ûÁ¥¦©£ ×e¤F Assignmen t e(m Z3 0 static Test monitor = new Test(); Aº§¨3×Ñ mon itor (( Test e(ñ]à Test ü simpletest (3×eF main ( ) (b¡º<g0 monitor.expect(new String[] { "1: n1.i: 9, n2.i: 47", "2: n1.i: 47, n2.i: 47", "3: n1.i: 27, n2.i: 27" }); Strin g "zh(â(?@©!â(MXº P©!]ª!QຫA"z18E¬Xx)¡ ºìíÄÅâg, simpletestYA®OºNâ©!( expect( ) ¦¡;¯£Ë<gx(©!, N umber e)tmain ( )§¨,e× N umber e(ñ] (n 1 n 2 )AW( i T°~J,X(L|P n 2 ~¬ n 1Áïð n 1FV°(áâË¡ÝE?| n 1 n 2 ý±:Æe× ²³(¥_F Java ˶`)¥ referen ce (~Læò expect( )ÝE<!¥ n 1 (ïðº g n 2 ´ æn 1 n 2 Æ referen ceà e× referen ce üy3×¥_(å/ Y×F n 1 ˶( referen cey(µ 9 ×L(¥_F~L^
程中,这个 reference被覆盖了,实际上就是被扔掉了;而它所指的那 个对象会被垃圾回收器清理掉。) 这种现象通常被称为 aliasing(别名效应),这是Java操控对象的基本方 法。但是如果你想避免 aliasing又应该怎么作呢?你可以进一步对具体 的数据成员进行赋值: n1,i= n2.i 与丢掉一个对象,再将n1和n2都绑到同一个对象相比,这样就能保留 两个相互独立的对象了。不过你很快就会知道直接操控对象内部的数据是 很麻烦的,而且有违OO设计的原则。这个课题不是一两句话就能讲清楚 的,所以我们把它放到附录A里,我们会在那里专门探讨 aliasing的。 在此期间,你只要知道为对象赋值可能会与你的初衷大相径庭就行了 方法调用期间的 aliasing 把对象传给方法的时候,也会有 aliasing的问题: //: c03: Passobject. java Passing objects to methods may not be what / you're used to. import com. bruceeckel. simpletest. class Letter i public class Passobject i static Test monitor =new Test ()i static void f(Letter y) t y public static void main(String[] args)t Letter x new letterof X. c System. out. println("1:xC:#+x c)i System. out. println ("2:xc:+ monitor. expect (new String[] i 在很多编程语言里,方法f()会在它的作用域里对参数 Letter y作一份 拷贝。但这里传的还是 reference,所以下面这行 第5页共48页
shhgs@wgqqh.com 5 ✁ ✂ 48 ✁ â* × referen ce T¶·,ñJT¸¹, àADy(Y ×¥_ºTº»¼½¾¿¢¹) í_·TU aliasing (°ÑØv) Java ¥_(G µ ÄÅ¡|ÀÁ al iasin g üvwÕZ1ÂÊ¡ÝE(3Ã¥Ä/ ("#%(~L0 n1.i = n2.i; Ź3×¥_Á n 1 n 2 ÆÆg3×¥_>8 ¦;µÇ e×>Ȳ³(¥_,X^¡CºÉÒ¥_("# ÊË(àmÌ OO ?@(ås ×4dX3e;©¿Í (DEWPAkg,5 A ËWºFYË ÎÏ al iasin g ( Fò@4¡ó%É¥_~LÝ;º¡(fÐÂ>ÑÒ, aliasin g P¥_Ó¬µ(M[ºm al iasin g (cd0 //: c03:PassObject.java // Passing objects to methods may not be what // you're used to. import com.bruceeckel.simpletest.*; class Letter { char c; } public class PassObject { static Test monitor = new Test(); static void f(Letter y) { y.c = 'z'; } public static void main(String[] args) { Letter x = new Letter(); x.c = 'a'; System.out.println("1: x.c: " + x.c); f(x); System.out.println("2: x.c: " + x.c); monitor.expect(new String[] { "1: x.c: a", "2: x.c: z" }); } } ///:~ FVáâ˵ f( )ºFA(1ÔË¥" Letter y 13ú ËÓ( referen ceDE¶