一刷 2000 册 (2003/08)  

《Practical Java》
中文版 (繁体)

侯捷 / 刘永丹 译

practical-java-b5.jpg (69294 bytes)

封面封底全图

本书范例  PraticalJavaCode.zip

繁体中文版勘误


□中译书名(暂名):Practical Java 中文版
□适合对象:中高阶 Java 程式员。
□制作特色:与原文本页页对译,含 index,网片输出,平装
□内容特色:68 条 Java编程准则与注意事项

开放档案如下:

档名 内容 大小 bytes
practical-java-chap1-3.pdf

不需密码即可开启。
档案含简略书签(目录连结)

扉页、译序、目录、细目、前言、致谢、1-3章
PraticalJavaCode.zip 本书范例源码
(原作者提供)
139,288

不需密码即可开启。档案不含书签(目录连结)

如欲下载,请将滑鼠移至上述 hyperlink,按右键,再选【另存目标...】即可。

译序
by 侯捷

面对Java,可从两方面看待,一是语言,一是平台。本书谈的是Java语言,以下我所言种种,也是指Java语言。

Java是一门优秀的物件导向编程语言(Object Oriented Programming Language, OOPL)。什麽是「物件导向」?如何才称得上「优秀」?前者可定量定性,客观;後者往往流於个人感受,主观!所以虽然物件导向语言有着几近一致的条件和门槛注1)(封装、继承、多型),孰优孰劣却是各人心中一把尺。尽管如此,无人可以否认Java语言在OOP(物件导向编程)上拥有良好的特性和优越的表现。

注1:我常忆起网络论坛上时可与闻的一种怪诞态度。有一派人士主张,OO是一种思想,一种思考模式,任何语言都能够实现它,因而侈言「C或assembly语言也能OO」。任何语言各有用途,这是完全正确的;OO是一种思维,这话也是对的。任何语言都能够实现OO,这话对某些人也许是对的,对99.9999%的人是错的。以non-OO语言实现OO思维,非但达成度极低,也非人人能为。Edmund Hillary(艾德蒙 希拉瑞)能达到的高度,你未必达得到 ─ 事实上你通常达不到。(注:Edmund Hillary是第一位登上圣母锋的地球人,1953年英格兰远征队员。)

我所谓良好的OOP特性,指的是Java提供了许多让程式员得以轻松表达物件导向技术与思维的语言关键字(keywords)如class, abstract, interface, extends, implements, public, protected, private, final, static, finalize┅,又提供条理清晰结构分明的档案组织方式如package, import,又拥有严谨而灵活的动态型别系统(dynamic type system)使得以提供RTTIReflection机制,并拥有一个优秀、涵盖面广、扩充性强的标准程式库(Java Libraries)。

这些优秀的语言构件(constructs)虽然好用易用,但不论就技术面或应用面或效率考量,还是有许多隐微细节散布其中,例如object creation, object initialization, Cloneable, Serializable, Equality, Immutability, Multithreading (Synchronization), Exception Handling┅,在在需要Java程式员深入认识与理解。

市面上Java书籍极多,专注於「编程主题式探讨」并「以独立条款呈现」的书籍比较少。这类书籍面向中高阶读者,不仅选题必须饶富价值、探讨必须极为深刻,各主题最好还独立以利选择阅读,却又最好彼此前後呼应环环相扣,并附良好交叉索引,予读者柳暗花明的强烈冲击。此种「专题条款」式的表现风格,在Scott Meyers的《Effective C++》和《More Effective C++》二书面世之後获得许多赞扬,也引来许多追随。

Practical Java》和《Effective Java》二书,对前述重要而基础的技术细微处有着详尽、深刻、实用的介绍和剖析和范例,又以独立条款之姿展现,在内容的扎实度、可读性、易读性上表现均十分良好。为此,秉持并承继我为C++ 社群翻译《Effective C++》、《More Effective C++》的态度和机缘,我很开心再次由我负责,将《Practical Java》和《Effective Java》二书中译本呈献给Java 社群。

考虑本书读者应已具备Java编程基础,对於各种英文术语已有良好的接受度,我在书中保留了许多英文术语,时而中英并陈,包括class, object, interface, reference, instance, array, vector, stack, heap┅,也包括涉及Java关键字的一些用语如private, public, protected, static, abstract┅,不胜枚举(下页另有一个扼要说明)。本书努力在字型变化上突显不同类形的术语,以利读者阅读。本书支援网站有一个「术语  英中繁简」对照表,欢迎访问,网址如下。

Practical Java》由刘永丹先生和我合力完成。永丹做前期初译工作,我负责後继的文字修润、技术检阅、大局风貌。永丹技术扎实,文字用心。没有他的协助,本书不可能在这个时间以这样的品质面世。谢谢永丹。

本书每一章起始处都有作者匠心独具收集的一些文摘语录。我们虽勉力译出,恐见识不足,贻笑大方,故均留下原文和出处,庶几不误读者。

侯捷 2003/07/08 于台湾.新竹

jjhou@jjhou.com(电子邮箱)
http://www.jjhou.com(繁体)(术语对照表http://www.jjhou.com/terms.htm
http://jjhou.csdn.net(简体) (术语对照表http:// jjhou.csdn.net/terms.htm

p.s. 本书已就英文版截至2003/07/08之勘误表修正於纸本。

■本书术语翻译与保留之大致原则:

广被大众接受之术语,无需额外说明,不在此列。例如继承(inheritance)、封装(encapsulation)、多型(polymorphism)。

本书保留与Java关键字相关之术语不译,例如class, interface, private, public, protected, static, final, abstract, synchronized, serializable┅

本书保留资料结构名称不译,例如array, vector, list, map, set, stack, heap┅"collection" 译为「群集」。

"class" 及其所衍生之各种名词如subclass, superclass, immutable class, mutable class, base class, derived class等皆保留不译(时而英中并陈)。"object" 大多数时候译为「物件」,时而保留。"object reference" 保留不译,"reference" 亦不译。

"type" 译为「型别」。"parameter" 译为「叁数」,"argument" 译为「引数」。"delegate", "delegation" 译为「委托」,"aggregate", "aggregation" 译为「聚合」。"composition" 译为「复合」。

动词 "create" 译为「创建」或「建立」,描述物件之初次诞生。动词 "refer" 译为「指涉」或「指向」或「引用」。动词 "dereference" 译为「提领」。动词 "override" 译为「覆写」。动词 "overload" 译为「重载」。

本书将Java class "methods" 译为函式,因为它等价於其他编程语言之 "function"。若直译为「方法」,行文缺乏术语突出感,恐影响阅读流畅;若不译,过於频繁出现又恐影响版面观感。

本书将Java class "fields" 译为栏位,等价於C++ 语言之 "data member"

本书将 "clone" 译为「克隆」(这一用词在中国大陆极为普遍),映照 "copy" 之於「拷贝」。非单纯保留 "clone" 是因为它时常做为动词并频繁出现,而我对术语的保留态度是尽量只考虑名词(偶有形容词)。

※「static栏位与instance栏位」、「reference物件与value物件」、「reference型别与primitive型别」等等术语保留部分英文,并使用特殊字型。

本书支援网站有一个「术语英中繁简」对照表,欢迎访问,网址见上页。

术语翻译有许多两难之处,祈愿读者体谅;译者勉力求取各方平衡,并尽可能於突兀处中英并陈。

-- 侯捷

细目
理论上,理论和实际并没有区别;实际上,它们是有区别的。
In theory, there is no difference between theory and practice. But, in practice, there is.
Jan L.A. van de Snepscheut

一般技术(General Techniques

实践1:引数是以传值(by value)而非传址(by reference)方式传递

所有Java objects都透过object reference而被取用。常见的一个误解是Javaby reference方式传递引数。事实上所有引数都以by value方式传递。

实践2:对不变的dataobject reference使用final

为了让dataobject reference成为不变量,请使用final。注意,final仅仅令object reference自身成为不变量,并不限制它所指向之物件的改变。

实践3:预设情况下所有non-static函式都可被覆写(overridden

预设情况下,所有non-static函式都可以被subclass覆写。但如果加上关键字final,便可防止被subclass覆写。

实践4:慎重选择arraysVectors

arraysvectors是常见的容器类别(storage classes)。选用它们之前应该先了解它们的功用和特性。

实践5:多型(polymorphism)优於instanceof

instanceof的许多用途可以因为改用多型而消除之。使用多型,程式码将更清晰、更易於扩展和维护。

实践6:必要时才使用instanceof

有时我们无法回避使用instanceof。我们应该了解什麽情况下必须使用它。

实践7:一旦不再需要object references,就将它设为null

不要忽视记忆体可能带来的问题。尽管有了垃圾回收机制(garbage collection),你仍然需要关注你的程式码如何运用记忆体。如果能够领悟垃圾回收机制和记忆体运用细节,你就能够更好地知道何时应该将object references设为null,那将导致高效的程式码。

物件与相等性(Objects and Equality

实践8:区分reference typeprimitive type

Java是物件导向的,但其操控的东西并非都是物件(objects)。理解reference typeprimitive types之间的差异,及它们在JVM中的表述(representation),会使你在运用它们时得以做出明智的选择。

实践9:区分 == equals()

== 用来测试基本型别的相等性,亦可判定两个object references是否指向同一个object。若要测试values(值)或semantic(语意)相等,应使用equals()

实践10:不要依赖equals()的预设实作(default implementation

不要不假思索地认定一个class总是会正确实作出equals()。此外,java.lang.Object提供的equals()大多数时候并非进行你想要的比较。

实践11:实作equals()时必须深思熟虑

如果某个class的两个objects「即使不占用相同的记忆体空间,也被视为逻辑上相等」,那麽就该为这个class提供一个equals()

实践12:实作equals()时优先考虑使用getClass()

实作equals()时请优先考虑采用getClass()。毕竟,「相同class下的objects才得被视为相等」是正确实作equals()的一个清晰简明的解决方案。

实践13:呼叫base class(基础类别)的super.equals()

任何base class(除了java.lang.Object)如果实作equals(),其derived class都应该呼叫super.equals()

实践14:在equals()函式中谨慎使用instanceof

唯有当你考虑允许「一个derived class object可以相等於其base class object」时,才在equals()中使用instanceof。使用这项技术前请先弄清楚其影响。

实践15:实作equals()时需遵循某些规则

撰写equals()并非那麽直观浅白。如果想要恰当地实作出equals(),请遵循某些规则。

异常处理(Exception Handling

实践16:认识「异常控制流」(exception control flow)机制

让自己谙晓异常控制流程细节。了解这些细微之处有助於你回避问题。

实践17:绝对不可轻忽异常(Never ignore an Exceptions

一旦异常出现却没有被捕获,抛出异常的那个执行绪(thread)就会中止运行。是的,异常意味错误,永远不要忽略它。

实践18:千万不要掩盖异常(Never hide an Exceptions

如果处理异常期间又从catchfinally区段抛出异常,原先的异常会因而被隐蔽起来。一旦发生这样的事情,就会丢失错误资讯。你应当撰写专门负责处理这种情形的程式码,将所有异常回传给呼叫者。

实践19:明察throws子句的缺点

将一个异常加入某函式的throws子句,会影响该函式的所有呼叫者。

实践20:细致而全面地理解throws子句

任何函式的throws子句应当列出它所传播的所有异常,包括衍生异常型别(derived exception types)。

实践21:使用finally避免资源泄漏(resource leaks

不要忽视记忆体以外的资源。垃圾回收机制不会替你释放它们。请使用finally确保记忆体以外的资源被释放。

实践22:不要从try区块中回返

不要从try区块中发出return指令,因为这个函式未必会立即从那儿回返。如果存在finally区段,它就会被执行起来并可能改变回传值。

实践23:将try/catch区块置於回圈(loop)之外

撰写含有异常处理的回圈时,请将trycatch区块置於回圈外部。在某些实作版本上,这会产生更快的执行码。

实践24:不要将异常(exceptions)用於流程控制

请将异常用於预期行为之外的情况。不要以异常来控制流程,请采用标准的语言流程构件(flow constructs),这样的流程表达会更清晰更高效。

实践25:不要每逢出错就使用异常(exceptions

只有面对程式行为可能出乎预料的情境下才使用异常。「预期中的行为」应使用回传码(return codes)来处理。

实践26:在建构式(constructors)中抛出异常

尽管建构式并非函式(method),因而不能回传一个值,但建构式有可能失败。如果它们失败了,请抛出一个异常。

实践27:抛出异常(exceptions)之前先将object恢复为有效状态

抛出异常很容易,困难的是「将异常所引发的伤害减到最小」。抛出异常之前,应确保「如果异常被处理好,流程再次进入抛出异常的那个函式中,该函式可以成功完成」。

效能/效率(Performance

实践28:先把焦点放在设计、资料结构和演算法身上

Java带来最大效能提升的办法就是:在设计和演算法中使用与语言无关的技术。因此,首先请将你的精力集中於这些上面。

实践29:不要倚赖编译期程式码优化技术

Java编译器生成的码,通常不会比你自己撰写的更好。别指望编译器能够多麽优化你的原始码。

实践30:理解运行期(runtime)程式码优化技术

Java效能的大部分努力都围绕着运行期优化展开。这种作法有利有弊。

实践31:如欲进行字串接合,StringBuffer优於String

对於字串接合,StringBuffer class要比String class快许多倍。

实践32:将object的创建成本(creation cost)降至最小

在许多物件导向系统中,「产生物件」意味着高昂的成本。了解成本所在,以及了解「加速物件产生速度」的技术,都可以导致更快的程式码。

实践33:慎防未用上的物件(unused objects

非必要别产生物件。非必要地产生物件,会减慢你的程式速度。

实践34:将同步(synchronization)减至最低

宣告synchronized函式或synchronized区块,会显着降低效能。只在物件需要时才使用同步机制(synchronization)。

实践35:尽可能使用stack变数

stack变数为JVM提供了更高效的byte code指令序列。所以在回圈内重复取用static变数或instance变数时,应当将它们临时储存於stack变数中,以便获得更快的执行速度。

实践36:使用staticfinalprivate函式以允许实施inlining

以函式本体替换函式呼叫,会导致更快的程式码。如果要令函式为inline,必须先宣告它们为staticfinalprivate

实践37instance变数的初始化只要一次就好

由於所有static变数和instance变数都会自动获得预设值,所以不必重新将它们设为预设值。

实践38:使用基本型别(primitive types)使程式码更快更小

使用基本型别,比使用基本型别外覆类别(wrapper),产生的程式码又小又快。

实践39:不要使用EnumerationIterator来巡访Vector

巡访Vector时,请使用get函式而非EnumerationIterator。这样做会导致更少的函式呼叫,意味程式码会更快。

实践40:使用System.arraycopy() 来复制arrays

请使用System.arraycopy() 来复制arrays。那是个原生(native)函式,速度最快。

实践41:优先使用array,然後才考虑VectorArrayList

如果可能,就使用array。如果你需要Vector的功能但不需要它的同步特性,可改用ArrayList

实践42:尽可能复用(reuseobjects

复用现有物件,几乎总是比产生新物件更划算。

实践43:使用缓式评估(延迟求值,lazy evaluation

如果某个成本高贵的计算并非一定必要,就尽量少做。请使用「缓式评估」
lazy evaluation,延迟求值)技术避免那些永远不需要的工作。

实践44:手工优化(optimize)你的程式码

由於Java编译器在优化方面的作为甚少,为了生成最佳byte code,请手工优化你的原始码。

实践45:编译为原生码(native code

编译为原生码,通常可以导致执行速度更快的程式码。但你却因此必须在各种不同的原生方案(native solution)中取舍。

多绪(Multithreading

实践46:面对instance函式,synchronized锁定的是物件而非函式或程式码

关键字synchronized锁定的是物件,而非函式或程式码。一个函式或程式
区段被宣告为
synchronized,并不意味同一时刻只能由一个执行绪执行它。

实践47:弄清楚synchronized staticssynchronized instance函式之间的差异

两个函式被宣告为synchronized,并不就意味它们是「执行绪安全」(thread-safe)的。对instance函式或object reference同步化,与对static函式或class literal(字面常数)同步化相比,得到的lock全然不同

实践48:以「private资料搭配存取器(accessor)」取代public/protected资料 

如果没有适当保护你的资料,用户便有机会绕过你的同步机制。

实践49:避免无谓的同步控制(avoid unnecessary synchronization

一般情况下请不要同步化所有函式。同步化不仅造成程式缓慢,并且丧失了
并行(
concurrency)的可能。请采用「单物件多锁」技术以允许更多并行。

实践50:取用共享变数时请使用synchronizedvolatile

不可切割(原子化,atomic)操作并非意味「执行绪安全」。JVM实作品被允许在私有记忆体中保留变数的工作副本。这可能会产生陈旧数值(stale values)。为避免这个问题,请使用同步化机制或将变数宣告为volatile

实践51:在单一操作(single operation)中锁定所有用到的objects

同步化某一函式,并不一定就会使其成为「执行绪安全」的函式码。如果synchronized函式操控着多个objects,而它们并不都是此函式所属classprivate instance data,那麽你必须对这些objects自身也进行同步化。

实践52:以固定而全域性的顺序取得多个locks(机锁)以避免死结(deadlock)

当你同步化多个物件,请以固定和全域性的顺序获得locks,以避免死结。

实践53:优先使用notifyAll()而非notify()

notify()只唤醒一个执行绪。要想唤醒多个执行绪,请使用notifyAll()

实践54:针对wait()notifyAll()使用旋锁(spin locks

当你等待条件变数(condition variables)时,请总是使用旋锁(spin locks)以确保正确结果。

实践55:使用wait()notifyAll()取代轮询回圈(polling loops

将所有polling loops替换为使用wait()notify()notifyAll()spin locks(旋锁)。使用spin locks直观而高效,使用polling loops则慢很多倍。

实践56:不要对locked object(上锁物件)之object reference重新赋值

当一个object被锁定,有可能其他执行绪会因同一个object lock而受阻(blocked)。假如你对上锁物件的object reference重新赋值,其他执行绪中悬而未决的那些locks将不再有意义。

实践57:不要呼叫stop()suspend()

不要呼叫stop()suspend(),因为它们可能导致资料内部混乱,甚至引发死结(deadlock)。

实践58:透过执行绪(threads)之间的合作来中止执行绪

你不应该呼叫stop()。如欲安全地停止执行绪,必须要求它们相互协作,才能姿态优雅地中止。

类别与介面(Classes and Interfaces

实践59:使用interface支援多重继承(multiple inheritance

当你想要支援interface的单一继承或多重继承,或者想要实作一个标记式的(markerinterface时,请使用interfaces

实践60:避免interfaces中的函式发生冲突

没有任何办法能够阻止两个interfaces使用同名的常数和函式。为了避免可能的冲突,应当小心命名常数和函式。

实践61:需要提供部分实作(partial implementation)时,请使用abstract classes

使用abstract class来为一个class提供部分实作,这些实作很可能对derived class是共通的(都被需要的)。

实践62:区分interfaceabstract classconcrete class

一旦正确理解了interfaceabstract classconcrete class的差异,你就可以在设计和撰码时做出正确的选择。

实践63:审慎地定义和实作immutable classes(恒常类别)

如果你希望object的资料永远不被改动,请使用immutable object。这种objects自动拥有执行绪安全性(thread safety)。

实践64:欲传递或接收mutable objects(可变物件)之object references时,请施行clone()

为了保证immutable objects,你必须在传入和回传immutable objects时对它们进行clone动作。

实践65:使用继承(inheritance)或委托(delegation)来定义 immutable classes(恒常类别)

使用immutable interfacecommon interfacebase class,或是immutable delegation classes,来定义immutable classes(恒常类别)。

实践66:实作clone()时记得呼叫super.clone()

当你实作了一个clone(),总是应该呼叫super.clone()以确保产生正确的object

实践67:别只是倚赖finalize()清理记忆体以外的(non-memory)资源

你不能保证finalize()是否被呼叫,以及何时被呼叫。因此,请专门实作一个public函式来释放记忆体以外的资源。

实践68:在建构式(constructors)内呼叫non-final函式时要当心

如果一个non-final函式被某个derived class覆写,在建构式中呼叫这个函式可能会导致不可预期的结果。

前言
让无知尽管信口开河吧,学习自有其价值
Let ignorance talk as it will, learning has its value.
J. de La Fontaine, The Use of Knowledge, Book viii, Fable 19

本书汇集了Java编程实践方面的建议、忠告、范例和讨论。本书的组织是一个个独立课程,每个课程谓之实践(PRAXIS,发音prak-sis),用以讨论特定主题。每个实践按各自独立的方式撰写。你可以从头阅读到尾,也可以挑选某些专题阅读。这种编排风格使你可以在短暂的闲暇中阅读此书。许多实践都少於5页,因此你可以在简短的时间内学习它们。

我在这本书中详细分析了某些设计(design)和编程(programming)方面的问题。我挑选主题的依据是编程实践上的有效(effective)和高效(efficient)性质。Java最被人抱怨的一点是效能(效率,performance),因此我以最大的篇幅讨论这一主题,探索使Java程式码执行得更有效率的技术。

我撰写本书,希望它能够作为指南,帮助你设计和撰码。它可以帮助你更全面地理解Java,让你撰写出更高效、更健壮和(或许最重要的是)更正确的程式码。

本书所有资讯都适用於各种Java编程,并不囿於伺服端(server)、客户端(client)或GUI(图形用户介面)编程。此外,你可以将这些资讯运用於Java的任一发行版本。

本书风格受了Scott Meyers所着的《Effective C++》和《More Effective C++》的影响。我发现他的风格对书籍组织非常有益,因此我决定采用类似的格式。

预期读者

本书是为已经掌握Java语言基础知识的程式员准备的。我假设读者已经具备Java语言和并行编程(concurrent programming)的工作经验,并理解物件导向(object-oreinted)的概念和术语。本书适用於「想获得如何高效使用Java之实用建议、讨论和范例」的程式员。

无论对Java编程老手或新手,本书都为他们提供了Java关键领域的资讯和讨论。本书提供充足的新资讯,即使经验丰富的程式员也能从考查他们业已熟悉的领域中获得极大收益。例如在某些场合,我以独特的方式讨论问题,帮助程式员以不同的方法思考,或使用与以往不同的角度看待事物。

初入门的程式员也可以从本书获益良多。我提供了讨论和范例,帮助他们消除许多常见的编程错误。我也澄清了某些常见的Java错误观念,并强调语言特性方面的某些问题。

本书组织

本书组织为六大部分。

  1. 一般技术 展现Java编程的数个基础领域,包括引数传递(parameter passing),arraysVectors以及垃圾回收(garbage collection)。
  2. 物件与相等性 研究物件(objects)和基础型别(primitive types),以及如何、为何为一个class实作equals()
  3. 异常处理 提供异常处理技术(exception handling techniques)的细致分析,并告诉你如何在你的程式码中高效加入异常处理机制。
  4. 效能(效率) 展示可用来改善程式码效能的诸多技术,并仔细审查JVMJava虚拟机器)、byte codeJITs(及时程式码生成器)。
  5. 多执行绪 涵盖执行绪模型(threading model)的诸多方面 它们对於建立健壮、可靠的多执行绪程式极为关键。
  6. 类别与介面 解释了interfacesabstract classesconcrete classes,以及何处、何时使用它们。这部分还详细讨论了immutable object(不可变物件)、cloning(克隆、复制)和finalization(终结)。

在上述各标题之下,是数量不等的相关专题。往往我会在不止一处讨论特定专题的某个属性。例如我在不同场合讨论了synchronized关键字的方方面面,每次讨论都涉及synchronized的不同特性。为此,我提供了扩展性甚强的交叉叁照,你可以由此得知何时阅读特定专题、何处存在相关资讯。

目录之後便是细目。这一部分包含所有实践标题和其页码,并附有每个实践的核心概要。你可以使用这个细目唤起你对专题的记忆,或用以找出某个专题。

附录内含一个已经证实的技术,可以进一步扩展你的Java知识。之後还有一份「进一步阅读」列表,列出关於Java、一般设计和编程方面的书籍和期刊。

三言两语话PRAXIS(实践)

PRAXIS(实践)一词,是我查询「得以概括本书所做工作」的词汇的结果。1982年的《American Heritage Dictionary》将PRAXIS定义为:The practical application or exercise of a branch of learning(实际应用或训练;学习的一个支脉)。这正是我希望在本书中达成的。

最确切的恐怕是《Webster's New Collegiate Dictionary》於1958年给出的一份定义:Practice, especially of an art, science, or technical occupation; opposite to theory(实际履行,尤指艺术、科学或技术领域;与理论遥相对应)。这个定义准确概括了本书精髓。那句opposite to theory更是画龙点睛。「理论」本身当然没错,但本书没有为它准备位置。

范例程式码

正文所列的所有程式码,都采用本书写作时可获得之Java最新版本加以编译和运行。所有程式码都曾经在Windows NT 4.0环境下以Sun Java 2 SDKStandard Editionv1.2.1完成编译和运行。如果你想要得到原始码,请在以下网址进行注册:

http://www.awl.com/cseng/register

该网页要求你输入一个独一无二的码,此码可在本书末尾标明为「How to Register Your Book」的页面找到。

提供反馈

欢迎读者对本书提供相关反馈资讯。任何建议、批评或臭虫报告,都请寄到PracticalJava@awl.com

希望本书让你觉得有用、可读,并且具备实用价值。

Peter Haggar
Research Triangle Park, North Carolina
November, 1999