致读者:
我从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日
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;
private WaterSource source;
private int i;
private float f;
public String toString() {
return
"valve1 = " + valve1 + "\n" +java笔记总结
"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);
"valve1 = null",
"valve2 = null",
"valve3 = null",
"valve4 = null",
"i = 0",
"f = 0.0",
"source = null"
});
}
} ///:~
这两个类都定义了一个特殊的方法:toString( )。以后你就会知道,所有非primitive对象都有一个toString( )方法,当编译器需要一个String而它却是一个对象的时候,编译器就会自动调用这个方法。所以
当编译器从String( )的:
"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.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();
System.out.println(b);
"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();

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