提交 67d054c0 编写于 作者: W wizardforcel

appendix

上级 b6121d54
......@@ -112,7 +112,7 @@ public class SortedWordCount {
`countWords()`中,每次从数据流中取出一个记号,而`ttype`信息的作用是判断对每个记号采取什么操作——因为记号可能代表一个行尾、一个数字、一个字符串或者一个字符。
找到一个记号后,会查询`Hashtable counts`,核实其中是否已经以“键”(`Key`)的形式包含了一个记号。若答案是肯定的,对应的`Counter`(计数器)对象就会增值,指出已找到该单词的另一个实例。若答案为否,则新建一个`Counter`——因为`Counter`构造器会将它的值初始化为1,正是我们计算单词数量时的要求。
找到一个记号后,会查询`Hashtable counts`,核实其中是否已经以“键”(`Key`)的形式包含了一个记号。若答案是肯定的,对应的`Counter`(计数器)对象就会自增,指出已找到该单词的另一个实例。若答案为否,则新建一个`Counter`——因为`Counter`构造器会将它的值初始化为1,正是我们计算单词数量时的要求。
`SortedWordCount`并不属于`Hashtable`(散列表)的一种类型,所以它不会继承。它执行的一种特定类型的操作,所以尽管`keys()``values()`方法都必须重新揭示出来,但仍不表示应使用那个继承,因为大量`Hashtable`方法在这里都是不适当的。除此以外,对于另一些方法来说(比如`getCounter()`——用于获得一个特定字符串的计数器;又如`sortedKeys()`——用于产生一个枚举),它们最终都改变了`SortedWordCount`接口的形式。
......
......@@ -267,7 +267,7 @@ public class PetCount {
在Java 1.0中,对`instanceof`有一个比较小的限制:只可将其与一个已命名的类型比较,不能同`Class`对象作对比。在上述例子中,大家可能觉得将所有那些`instanceof`表达式写出来是件很麻烦的事情。实际情况正是这样。但在Java 1.0中,没有办法让这一工作自动进行——不能创建`Class`的一个`Vector`,再将其与之比较。大家最终会意识到,如编写了数量众多的`instanceof`表达式,整个设计都可能出现问题。
当然,这个例子只是一个构想——最好在每个类型里添加一个`static`数据成员,然后在构造器中令其增值,以便跟踪计数。编写程序时,大家可能想象自己拥有类的源码控制权,能够自由改动它。但由于实际情况并非总是这样,所以RTTI显得特别方便。
当然,这个例子只是一个构想——最好在每个类型里添加一个`static`数据成员,然后在构造器中令其自增,以便跟踪计数。编写程序时,大家可能想象自己拥有类的源码控制权,能够自由改动它。但由于实际情况并非总是这样,所以RTTI显得特别方便。
1. 使用类标记
......
......@@ -58,7 +58,7 @@ public class Alias1 {
Alias1 y = x; // Assign the handle
```
它会新建一个`Alias1`引用,但不是把它分配给由new创建的一个新鲜对象,而是分配给一个现有的引用。所以引用x的内容——即对象`x`指向的地址——被分配给`y`,所以无论`x`还是`y`都与相同的对象连接起来。这样一来,一旦`x``i`在下述语句中增值
它会新建一个`Alias1`引用,但不是把它分配给由new创建的一个新鲜对象,而是分配给一个现有的引用。所以引用x的内容——即对象`x`指向的地址——被分配给`y`,所以无论`x`还是`y`都与相同的对象连接起来。这样一来,一旦`x``i`在下述语句中自增
```
x.i++;
......
......@@ -61,7 +61,7 @@ public class Counter1 extends Applet {
} ///:~
```
在这个程序中,AWT和程序片代码都应是大家熟悉的,第13章对此已有很详细的交待。`go()`方法正是程序全心全意服务的对待:将当前的`count`(计数)值置入`TextField`(文本字段)`t`,然后使`count`增值
在这个程序中,AWT和程序片代码都应是大家熟悉的,第13章对此已有很详细的交待。`go()`方法正是程序全心全意服务的对待:将当前的`count`(计数)值置入`TextField`(文本字段)`t`,然后使`count`自增
`go()`内的部分无限循环是调用`sleep()``sleep()`必须同一个`Thread`(线程)对象关联到一起,而且似乎每个应用程序都有部分线程同它关联(事实上,Java本身就是建立在线程基础上的,肯定有一些线程会伴随我们写的应用一起运行)。所以无论我们是否明确使用了线程,都可利用`Thread.currentThread()`产生由程序使用的当前线程,然后为那个线程调用`sleep()`。注意,`Thread.currentThread()``Thread`类的一个静态方法。
......@@ -583,6 +583,6 @@ public class Daemons {
} ///:~
```
Daemon线程可将自己的Daemon标记设置成“真”,然后产生一系列其他线程,而且认为它们也具有Daemon属性。随后,它进入一个无限循环,在其中调用`yield()`,放弃对其他进程的控制。在这个程序早期的一个版本中,无限循环会使`int`计数器增值,但会使整个程序都好象陷入停顿状态。换用`yield()`后,却可使程序充满“活力”,不会使人产生停滞或反应迟钝的感觉。
Daemon线程可将自己的Daemon标记设置成“真”,然后产生一系列其他线程,而且认为它们也具有Daemon属性。随后,它进入一个无限循环,在其中调用`yield()`,放弃对其他进程的控制。在这个程序早期的一个版本中,无限循环会使`int`计数器自增,但会使整个程序都好象陷入停顿状态。换用`yield()`后,却可使程序充满“活力”,不会使人产生停滞或反应迟钝的感觉。
一旦`main()`完成自己的工作,便没有什么能阻止程序中断运行,因为这里运行的只有Daemon线程。所以能看到启动所有Daemon线程后显示出来的结果,`System.in`也进行了相应的设置,使程序中断前能等待一个回车。如果不进行这样的设置,就只能看到创建Daemon线程的一部分结果(试试将`readLine()`代码换成不同长度的`sleep()`调用,看看会有什么表现)。
......@@ -7,7 +7,7 @@
## 14.2.1 资源访问的错误方法
现在考虑换成另一种方式来使用本章频繁见到的计数器。在下面的例子中,每个线程都包含了两个计数器,它们在`run()`增值以及显示。除此以外,我们使用了`Watcher`类的另一个线程。它的作用是监视计数器,检查它们是否保持相等。这表面是一项无意义的行动,因为如果查看代码,就会发现计数器肯定是相同的。但实际情况却不一定如此。下面是程序的第一个版本:
现在考虑换成另一种方式来使用本章频繁见到的计数器。在下面的例子中,每个线程都包含了两个计数器,它们在`run()`自增以及显示。除此以外,我们使用了`Watcher`类的另一个线程。它的作用是监视计数器,检查它们是否保持相等。这表面是一项无意义的行动,因为如果查看代码,就会发现计数器肯定是相同的。但实际情况却不一定如此。下面是程序的第一个版本:
```
//: Sharing1.java
......@@ -148,9 +148,9 @@ public class Sharing1 extends Applet {
和往常一样,每个计数器都包含了自己的显示组件:两个文本字段以及一个标签。根据它们的初始值,可知道计数是相同的。这些组件在`TwoCounter`构造器加入`Container`。由于这个线程是通过用户的一个“按下按钮”操作启动的,所以`start()`可能被多次调用。但对一个线程来说,对`Thread.start()`的多次调用是非法的(会产生异常)。在`started`标记和重载的`start()`方法中,大家可看到针对这一情况采取的防范措施。
`run()`中,`count1``count2`增值与显示方式表面上似乎能保持它们完全一致。随后会调用`sleep()`;若没有这个调用,程序便会出错,因为那会造成CPU难于交换任务。
`run()`中,`count1``count2`自增与显示方式表面上似乎能保持它们完全一致。随后会调用`sleep()`;若没有这个调用,程序便会出错,因为那会造成CPU难于交换任务。
`synchTest()`方法采取的似乎是没有意义的行动,它检查`count1`是否等于`count2`;如果不等,就把标签设为`"Unsynched"`(不同步)。但是首先,它调用的是类`Sharing1`的一个静态成员,以便增值和显示一个访问计数器,指出这种检查已成功进行了多少次(这样做的理由会在本例的其他版本中变得非常明显)。
`synchTest()`方法采取的似乎是没有意义的行动,它检查`count1`是否等于`count2`;如果不等,就把标签设为`"Unsynched"`(不同步)。但是首先,它调用的是类`Sharing1`的一个静态成员,以便自增和显示一个访问计数器,指出这种检查已成功进行了多少次(这样做的理由会在本例的其他版本中变得非常明显)。
`Watcher`类是一个线程,它的作用是为处于活动状态的所有`TwoCounter`对象都调用`synchTest()`。其间,它会对`Sharing1`对象中容纳的数组进行遍历。可将`Watcher`想象成它掠过`TwoCounter`对象的肩膀不断地“偷看”。
......
......@@ -84,7 +84,7 @@ class Peeker extends Thread {
`Blockable`类打算成为本例所有类的一个基类。一个`Blockable`对象包含了一个名为`state``TextField`(文本字段),用于显示出对象有关的信息。用于显示这些信息的方法叫作`update()`。我们发现它用`getClass.getName()`来产生类名,而不是仅仅把它打印出来;这是由于`update(0)`不知道自己为其调用的那个类的准确名字,因为那个类是从`Blockable`派生出来的。
`Blockable`中,变动指示符是一个`int i`;派生类的`run()`方法会为其增值
`Blockable`中,变动指示符是一个`int i`;派生类的`run()`方法会为其自增
针对每个`Bloackable`对象,都会启动`Peeker`类的一个线程。`Peeker`的任务是调用`read()`方法,检查与自己关联的`Blockable`对象,看看i是否发生了变化,最后用它的`status`文本字段报告检查结果。注意`read()``update()`都是同步的,要求对象的锁定能自由解除,这一点非常重要。
......
......@@ -170,7 +170,7 @@ Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]
这意味着客户已用自己的本地端口1077与`127.0.0.1`机器上的端口8080建立了 连接。
大家会注意到每次重新启动客户程序的时候,本地端口的编号都会增加。这个编号从1025(刚好在系统保留的1-1024之外)开始,并会一直增加下去,除非我们重启机器。若重新启动机器,端口号仍然会从1025开始增值(在Unix机器中,一旦超过保留的套按字范围,数字就会再次从最小的可用数字开始)。
大家会注意到每次重新启动客户程序的时候,本地端口的编号都会增加。这个编号从1025(刚好在系统保留的1-1024之外)开始,并会一直增加下去,除非我们重启机器。若重新启动机器,端口号仍然会从1025开始自增(在Unix机器中,一旦超过保留的套按字范围,数字就会再次从最小的可用数字开始)。
创建好`Socket`对象后,将其转换成`BufferedReader``PrintWriter的`过程便与在服务器中相同(同样地,两种情况下都要从一个`Socket`开始)。在这里,客户通过发出字符串`"howdy"`,并在后面跟随一个数字,从而初始化通信。注意缓冲区必须再次刷新(这是自动发生的,通过传递给`PrintWriter`构造器的第二个参数)。若缓冲区没有刷新,那么整个会话(通信)都会被挂起,因为用于初始化的`"howdy"`永远不会发送出去(缓冲区不够满,不足以造成发送动作的自动进行)。从服务器返回的每一行都会写入`System.out`,以验证一切都在正常运转。为中止会话,需要发出一个`"END"`。若客户程序简单地挂起,那么服务器会“抛”出一个异常。
......
......@@ -177,4 +177,4 @@ public class MultiJabberClient {
`JabberClientThread`构造器获取一个`InetAddress`,并用它打开一个套接字。大家可能已看出了这样的一个套路:`Socket`肯定用于创建某种`Reader`以及/或者`Writer`(或者`InputStream`和/或`OutputStream`)对象,这是运用`Socket`的唯一方式(当然,我们可考虑编写一、两个类,令其自动完成这些操作,避免大量重复的代码编写工作)。同样地,`start()`执行线程的初始化,并调用`run()`。在这里,消息发送给服务器,而来自服务器的信息则在屏幕上回显出来。然而,线程的“存在时间”是有限的,最终都会结束。注意在套接字创建好以后,但在构造器完成之前,假若构造器失败,套接字会被清除。否则,为套接字调用`close()`的责任便落到了`run()`方法的头上。
`threadcount`跟踪计算目前存在的`JabberClientThread`对象的数量。它将作为构造器的一部分增值,并在`run()`退出时减值`run()`退出意味着线程中止)。在`MultiJabberClient.main()`中,大家可以看到线程的数量会得到检查。若数量太多,则多余的暂时不创建。方法随后进入“休眠”状态。这样一来,一旦部分线程最后被中止,多作的那些线程就可以创建了。大家可试验一下逐渐增大`MAX_THREADS`,看看对于你使用的系统来说,建立多少线程(连接)才会使您的系统资源降低到危险程度。
`threadcount`跟踪计算目前存在的`JabberClientThread`对象的数量。它将作为构造器的一部分自增,并在`run()`退出时自减`run()`退出意味着线程中止)。在`MultiJabberClient.main()`中,大家可以看到线程的数量会得到检查。若数量太多,则多余的暂时不创建。方法随后进入“休眠”状态。这样一来,一旦部分线程最后被中止,多作的那些线程就可以创建了。大家可试验一下逐渐增大`MAX_THREADS`,看看对于你使用的系统来说,建立多少线程(连接)才会使您的系统资源降低到危险程度。
......@@ -65,7 +65,7 @@ StaticTest st2 = new StaticTest();
StaticTest.i++;
```
其中,`++`运算符会使变量增值。此时,无论`st1.i`还是`st2.i`的值都是48。
其中,`++`运算符会使变量自增。此时,无论`st1.i`还是`st2.i`的值都是48。
类似的逻辑也适用于静态方法。既可象对其他任何方法那样通过一个对象引用静态方法,亦可用特殊的语法格式`类名.方法()`加以引用。静态方法的定义是类似的:
......@@ -75,7 +75,7 @@ static void incr() { StaticTest.i++; }
}
```
从中可看出,`StaticFun`的方法`incr()`使静态数据`i`增值。通过对象,可用典型的方法调用`incr()`
从中可看出,`StaticFun`的方法`incr()`使静态数据`i`自增。通过对象,可用典型的方法调用`incr()`
```
StaticFun sf = new StaticFun();
......
# Summary
* [Introduction](README.md)
* [Java 编程思想](README.md)
* [写在前面的话](写在前面的话.md)
* [引言](引言.md)
* [第1章 对象入门](第1章 对象入门.md)
......
......@@ -870,15 +870,15 @@ paint()方法由简单的开始:它用按钮的颜色填充了一个“圆角
(3) 对话框是不被信任的。在Java中,对话框存在一些令人难解的地方。首先,它们不能正确地拒绝程序片,这实在是令人沮丧。如果我们从程序片弹出一个对话框,我们会在对话框上看到一个附上的消息框“不被信任的程序片”。这是因为在理论上,它有可能欺骗用户去考虑他们在通过WEB同一个老顾客的本地应用程序交易并且让他们输入他们的信用卡号。在看到AWT开发的那种GUI后,我们可能会难过地相信任何人都会被那种方法所愚弄。但程序片是一直附着在一个Web页面上的,并可以在浏览器中看到,而对话框没有这种依附关系,所以理论上是可能的。因此,我们很少会见到一个使用对话框的程序片。
在较新的浏览器中,对受到信任的程序片来说,许多限制都被放宽了(受信任程序片由一个信任源认证)。
涉及程序片的开发时,还有另一些问题需要考虑:
程序片不停地从一个适合不同类的单独的服务器上下载。我们的浏览器能够缓存程序片,但这没有保证。在Java 1.1版中的一个改进是JAR(Java ARchive)文件,它允许将所有的程序片组件(包括其它的类文件、图像、声音)一起打包到一个的能被单个服务器处理下载的压缩文件。“数字签字”(能校验类创建器)可有效地加入每个单独的JAR文件。
因为安全方面的缘故,我们做某些工作更加困难,例如访问数据库和发送电子邮件。另外,安全限制规则使访问多个主机变得非常的困难,因为每一件事都必须通过WEB服务器路由,形成一个性能瓶颈,并且单一环节的出错都会导致整个处理的停止。
浏览器里的程序片不会拥有同样的本地应用程序运行的控件类型。例如,自从用户可以开关页面以来,在程序片中不会拥有一个形式上的对话框。当用户对一个WEB页面进行改变或退出浏览器时,对我们的程序片而言简直是一场灾难——这时没有办法保存状态,所以如果我们在处理和操作中时,信息会被丢失。另外,当我们离开一个WEB页面时,不同的浏览器会对我们的程序片做不同的操作,因此结果本来就是不确定的。
+ 程序片不停地从一个适合不同类的单独的服务器上下载。我们的浏览器能够缓存程序片,但这没有保证。在Java 1.1版中的一个改进是JAR(Java ARchive)文件,它允许将所有的程序片组件(包括其它的类文件、图像、声音)一起打包到一个的能被单个服务器处理下载的压缩文件。“数字签字”(能校验类创建器)可有效地加入每个单独的JAR文件。
+ 因为安全方面的缘故,我们做某些工作更加困难,例如访问数据库和发送电子邮件。另外,安全限制规则使访问多个主机变得非常的困难,因为每一件事都必须通过WEB服务器路由,形成一个性能瓶颈,并且单一环节的出错都会导致整个处理的停止。
+ 浏览器里的程序片不会拥有同样的本地应用程序运行的控件类型。例如,自从用户可以开关页面以来,在程序片中不会拥有一个形式上的对话框。当用户对一个WEB页面进行改变或退出浏览器时,对我们的程序片而言简直是一场灾难——这时没有办法保存状态,所以如果我们在处理和操作中时,信息会被丢失。另外,当我们离开一个WEB页面时,不同的浏览器会对我们的程序片做不同的操作,因此结果本来就是不确定的。
13.14.1 程序片的优点
如果能容忍那些限制,那么程序片的一些优点也是非常突出的,尤其是在我们构建客户/服务器应用或者其它网络应用时:
没有安装方面的争议。程序片拥有真正的平台独立性(包括容易地播放声音文件等能力)所以我们不需要针对不同的平台修改代码也不需要任何人根据安装运行任何的“tweaking”。事实上,安装每次自动地将WEB页连同程序片一起,因此安静、自动地更新。在传统的客户端/服务器系统中,建立和安装一个新版本的客户端软件简直就是一场恶梦。
因为安全的原因创建在核心Java语言和程序片结构中,我们不必担心坏的代码而导致毁坏某人的系统。这样,连同前面的优点,可使用Java(可从JavaScript和VBScript中选择客户端的WEB编程工具)为所谓的Intrant(在公司内部使用而不向Internet转移的企业内部网络)客户端/服务器开发应用程序。
由于程序片是自动同HTML集成的,所以我们有一个内建的独立平台文件系统去支持程序片。这是一个很有趣的方法,因为我们惯于拥有程序文件的一部分而不是相反的拥有文件系统。
+ 没有安装方面的争议。程序片拥有真正的平台独立性(包括容易地播放声音文件等能力)所以我们不需要针对不同的平台修改代码也不需要任何人根据安装运行任何的“tweaking”。事实上,安装每次自动地将WEB页连同程序片一起,因此安静、自动地更新。在传统的客户端/服务器系统中,建立和安装一个新版本的客户端软件简直就是一场恶梦。
+ 因为安全的原因创建在核心Java语言和程序片结构中,我们不必担心坏的代码而导致毁坏某人的系统。这样,连同前面的优点,可使用Java(可从JavaScript和VBScript中选择客户端的WEB编程工具)为所谓的Intrant(在公司内部使用而不向Internet转移的企业内部网络)客户端/服务器开发应用程序。
+ 由于程序片是自动同HTML集成的,所以我们有一个内建的独立平台文件系统去支持程序片。这是一个很有趣的方法,因为我们惯于拥有程序文件的一部分而不是相反的拥有文件系统。
13.15 视窗化应用
出于安全的缘故,我们会看到在程序片我们的行为非常的受到限制。我们真实地感到,程序片是被临时地加入在WEB浏览器中的,因此,它的功能连同它的相关知识,控件都必须加以限制。但是,我们希望Java能制造一个开窗口的程序去运行一些事物,否则宁愿安放在一个WEB页面上,并且也许我们希望它可以运行一些可靠的应用程序,以及夸张的实时便携性。在这本书前面的章节中我们制造了一些命令行应用程序,但在一些操作环境中(例如:Macintosh)没有命令行。所以我们有很多的理由去利用Java创建一个设置窗口,非程序片的程序。这当然是一个十分合理的要求。
......@@ -4767,17 +4767,17 @@ makePanel()方法获取我们想创建的类Class对象和用newInstance()去创
13.19.17 Swing更多的知识
这一节意味着唯一向我们介绍的是Swing的强大力量和我们的着手处,因此我们能注意到通过库,我们会感觉到我们的方法何等的简单。到目前为止,我们已看到的可能足够满足我们UI设计需要的一部分。不过,这里有许多有关Swing额外的情况——它有意成为一全功能的UI设计工具箱。如果我们没有发现我们所需要的,请到SUN公司的在线文件中去查找,并搜索WEB。这个方法几乎可以完成我们能想到的任何事。
本节中没有涉及的一些要点:
更多特殊的组件,例如JColorChooser,JFileChooser,JPasswordField,JHTMLPane(完成简单的HTML格式化和显示)以及JTextPane(一个支持格式化,字处理和图像的文字编辑器)。它们都非常易用。
Swing的新的事件类型。在一些方法中,它们看起来像异常:类型非常的重要,名字可以被用来表示除了它们自己之外的任何事物。
新的布局管理:Springs & Struts以及BoxLayout
分裂控制:一个间隔物式的分裂条,允许我们动态地处理其它组件的位置。
JLayeredPane和JInternalFrame被一起用来在当前帧中创建子帧,以产生多文件接口(MDI)应用程序。
可插入的外观和效果,因此我们可以编写单个的程序可以像期望的那样动态地适合不同的平台和操作系统。
自定义光标。
JToolbar API提供的可拖动的浮动工具条。
双缓存和为平整屏幕重新画线的自动重画批次。
内建“取消”支持。
拖放支持。
+ 更多特殊的组件,例如JColorChooser,JFileChooser,JPasswordField,JHTMLPane(完成简单的HTML格式化和显示)以及JTextPane(一个支持格式化,字处理和图像的文字编辑器)。它们都非常易用。
+ Swing的新的事件类型。在一些方法中,它们看起来像异常:类型非常的重要,名字可以被用来表示除了它们自己之外的任何事物。
+ 新的布局管理:Springs & Struts以及BoxLayout
+ 分裂控制:一个间隔物式的分裂条,允许我们动态地处理其它组件的位置。
+ JLayeredPane和JInternalFrame被一起用来在当前帧中创建子帧,以产生多文件接口(MDI)应用程序。
+ 可插入的外观和效果,因此我们可以编写单个的程序可以像期望的那样动态地适合不同的平台和操作系统。
+ 自定义光标。
+ JToolbar API提供的可拖动的浮动工具条。
+ 双缓存和为平整屏幕重新画线的自动重画批次。
+ 内建“取消”支持。
+ 拖放支持。
13.20 总结
对于AWT而言,Java 1.1到Java 1.2最大的改变就是Java中所有的库。Java 1.0版的AWT曾作为目前见过的最糟糕的一个设计被彻底地批评,并且当它允许我们在创建小巧精致的程序时,产生的GUI“在所有的平台上都同样的平庸”。它与在特殊平台上本地应用程序开发工具相比也是受到限制的,笨拙的并且也是不友好的。当Java 1.1版纳入新的事件模型和Java Beans时,平台被设置——现在它可以被拖放到可视化的应用程序构建工具中,创建GUI组件。另外,事件模型的设计和Bean无疑对轻松的编程和可维护的代码都非常的在意(这些在Java 1.0 AWT中不那么的明显)。但直至GUI组件-JFC/Swing类-显示工作结束它才这样。对于Swing组件而言,交叉平台GUI编程可以变成一种有教育意义的经验。
......
此差异已折叠。
此差异已折叠。
......@@ -10,9 +10,9 @@ ThisIsAClassName
thisIsMethodOrFieldName
```
若在定义中出现了常数初始化字符,则大写static final基本类型识别符中的所有字母。这样便可标志出它们属于编译期的常数。
若在定义中出现了常数初始化字符,则大写`static final`基本类型识别符中的所有字母。这样便可标志出它们属于编译期的常数。
Java包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此。对于域名扩展名称,如com,org,net或者edu等,全部都应小写(这也是Java 1.1和Java 1.2的区别之一)。
Java包(`Package`)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此。对于域名扩展名称,如`com,org,net`或者`edu`等,全部都应小写(这也是Java 1.1和Java 1.2的区别之一)。
(2) 为了常规用途而创建一个类时,请采取“经典形式”,并包含对下述元素的定义:
......@@ -24,7 +24,7 @@ clone()(implement Cloneable)
implement Serializable
```
(3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。
(3) 对于自己创建的每一个类,都考虑置入一个`main()`,其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。
(4) 应将方法设计成简要的、功能性单元,用它描述和实现一个不连续的类接口部分。理想情况下,方法应简明扼要。若长度很大,可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用(有些时候,方法必须非常大,但它们仍应只做同样的一件事情)。
......@@ -32,13 +32,13 @@ implement Serializable
(6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议:
一个复杂的开关语句:考虑采用“多态”机制
+ 一个复杂的开关语句:考虑采用“多态”机制
数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现
+ 数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现
许多成员变量在特征上有很大的差别:考虑使用几个类
+ 许多成员变量在特征上有很大的差别:考虑使用几个类
(7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。
(7) 让一切东西都尽可能地“私有”——`private`。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有`private`字段才能在非同步使用的情况下受到保护。
(8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。
......@@ -46,21 +46,21 @@ implement Serializable
(10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作(参见第14章14.1.2小节的“用内部类改进代码”)。
(11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。
(11) 尽可能细致地加上注释,并用`javadoc`注释文档语法生成自己的程序文档。
(12) 避免使用“魔术数字”,这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。
(13) 涉及构造器和异常的时候,通常希望重新丢弃在构造器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。
(14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersOnExit(true),从而确保这一行为)。
(14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于`cleanup()`这样的名字,明确表明自己的用途。除此以外,可在类内放置一个`boolean`(布尔)标记,指出对象是否已被清除。在类的`finalize()`方法里,请确定对象已被清除,并已丢弃了从`RuntimeException`继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定`finalize()`能够在自己的系统中工作(可能需要调用`System.runFinalizersOnExit(true)`,从而确保这一行为)。
(15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。
(15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有`finally`从句的`try`块,开始清除工作。
(16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基类组件的时候它们依然有效。
(16) 若在初始化过程中需要覆盖(取消)`finalize()`,请记住调用`super.finalize()`(若`Object`属于我们的直接超类,则无此必要)。在对`finalize()`进行覆盖的过程中,对`super.finalize()`的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基类组件的时候它们依然有效。
(17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象“转换”到数组里。
(18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实现细节。
(18) 尽量使用`interfaces`,不要使用`abstract`类。若已知某样东西准备成为一个基类,那么第一个选择应是将其变成一个`interface`(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个`abstract`(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实现细节。
(19) 在构造器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结果(参见第7章的详细说明)。
......@@ -70,7 +70,7 @@ implement Serializable
(22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个“颜色”字段。
(23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。
(23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的`.class`文件。
(24) 在Java 1.1 AWT中使用事件“适配器”时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。
......@@ -88,4 +88,4 @@ implement Serializable
(31) 可在Web上找到大量的编程参考资源,甚至包括大量新闻组、讨论组、邮寄列表等。下面这个地方提供了大量有益的链接:
http://www.ulb.ac.be/esp/ip-Links/Java/joodcs/mm-WebBiblio.html
\ No newline at end of file
http://www.ulb.ac.be/esp/ip-Links/Java/joodcs/mm-WebBiblio.html
此差异已折叠。
......@@ -7,7 +7,7 @@
我之所以想到速度,部分原因是由于C++模型。C++将自己的主要精力放在编译期间“静态”发生的所有事情上,所以程序的运行期版本非常短小和快速。C++也直接建立在C模型的基础上(主要为了向后兼容),但有时仅仅由于它在C中能按特定的方式工作,所以也是C++中最方便的一种方法。最重要的一种情况是C和C++对内存的管理方式,它是某些人觉得Java速度肯定慢的重要依据:在Java中,所有对象都必须在内存“堆”里创建。
而在C++中,对象是在栈中创建的。这样可达到更快的速度,因为当我们进入一个特定的作用域时,栈指针会向下移动一个单位,为那个作用域内创建的、以栈为基础的所有对象分配存储空间。而当我们离开作用域的时候(调用完毕所有局部构造器后),栈指针会向上移动一个单位。然而,在C++里创建“内存堆”(Heap)对象通常会慢得多,因为它建立在C的内存堆基础上。这种内存堆实际是一个大的内存池,要求必须进行再循环(复用)。在C++里调用delete以后,释放的内存会在堆里留下一个洞,所以再调用new的时候,存储分配机制必须进行某种形式的搜索,使对象的存储与堆内任何现成的洞相配,否则就会很快用光堆的存储空间。之所以内存堆的分配会在C++里对性能造成如此重大的性能影响,对可用内存的搜索正是一个重要的原因。所以创建基于栈的对象要快得多。
而在C++中,对象是在栈中创建的。这样可达到更快的速度,因为当我们进入一个特定的作用域时,栈指针会向下移动一个单位,为那个作用域内创建的、以栈为基础的所有对象分配存储空间。而当我们离开作用域的时候(调用完毕所有局部构造器后),栈指针会向上移动一个单位。然而,在C++里创建“内存堆”(Heap)对象通常会慢得多,因为它建立在C的内存堆基础上。这种内存堆实际是一个大的内存池,要求必须进行再循环(复用)。在C++里调用`delete`以后,释放的内存会在堆里留下一个洞,所以再调用`new`的时候,存储分配机制必须进行某种形式的搜索,使对象的存储与堆内任何现成的洞相配,否则就会很快用光堆的存储空间。之所以内存堆的分配会在C++里对性能造成如此重大的性能影响,对可用内存的搜索正是一个重要的原因。所以创建基于栈的对象要快得多。
同样地,由于C++如此多的工作都在编译期间进行,所以必须考虑这方面的因素。但在Java的某些地方,事情的发生却要显得“动态”得多,它会改变模型。创建对象的时候,垃圾收集器的使用对于提高对象创建的速度产生了显著的影响。从表面上看,这种说法似乎有些奇怪——存储空间的释放会对存储空间的分配造成影响,但它正是JVM采取的重要手段之一,这意味着在Java中为堆对象分配存储空间几乎能达到与C++中在栈里创建存储空间一样快的速度。
......@@ -15,7 +15,7 @@
现在,大家可能注意到了堆事实并非一条传送带。如按那种方式对待它,最终就要求进行大量的页交换(这对性能的发挥会产生巨大干扰),这样终究会用光内存,出现内存分页错误。所以这儿必须采取一个技巧,那就是著名的“垃圾收集器”。它在收集“垃圾”的同时,也负责压缩堆里的所有对象,将“堆指针”移至尽可能靠近传送带开头的地方,远离发生(内存)分页错误的地点。垃圾收集器会重新安排所有东西,使其成为一个高速、无限自由的堆模型,同时游刃有余地分配存储空间。
为真正掌握它的工作原理,我们首先需要理解不同垃圾收集器(GC)采取的工作方案。一种简单、但速度较慢的GC技术是引用计数。这意味着每个对象都包含了一个引用计数器。每当一个引用同一个对象连接起来时,引用计数器就会增值。每当一个引用超出自己的作用域,或者设为null时,引用计数就会减值。这样一来,只要程序处于运行状态,就需要连续进行引用计数管理——尽管这种管理本身的开销比较少。垃圾收集器会在整个对象列表中移动巡视,一旦它发现其中一个引用计数成为0,就释放它占据的存储空间。但这样做也有一个缺点:若对象相互之间进行循环引用,那么即使引用计数不是0,仍有可能属于应收掉的“垃圾”。为了找出这种自引用的组,要求垃圾收集器进行大量额外的工作。引用计数属于垃圾收集的一种类型,但它看起来并不适合在所有JVM方案中采用。
为真正掌握它的工作原理,我们首先需要理解不同垃圾收集器(GC)采取的工作方案。一种简单、但速度较慢的GC技术是引用计数。这意味着每个对象都包含了一个引用计数器。每当一个引用同一个对象连接起来时,引用计数器就会自增。每当一个引用超出自己的作用域,或者设为`null`时,引用计数就会自减。这样一来,只要程序处于运行状态,就需要连续进行引用计数管理——尽管这种管理本身的开销比较少。垃圾收集器会在整个对象列表中移动巡视,一旦它发现其中一个引用计数成为0,就释放它占据的存储空间。但这样做也有一个缺点:若对象相互之间进行循环引用,那么即使引用计数不是0,仍有可能属于应收掉的“垃圾”。为了找出这种自引用的组,要求垃圾收集器进行大量额外的工作。引用计数属于垃圾收集的一种类型,但它看起来并不适合在所有JVM方案中采用。
在速度更快的方案里,垃圾收集并不建立在引用计数的基础上。相反,它们基于这样一个原理:所有非死锁的对象最终都肯定能回溯至一个引用,该引用要么存在于栈中,要么存在于静态存储空间。这个回溯链可能经历了几层对象。所以,如果从栈和静态存储区域开始,并经历所有引用,就能找出所有活动的对象。对于自己找到的每个引用,都必须跟踪到它指向的那个对象,然后跟随那个对象中的所有引用,“跟踪追击”到它们指向的对象……等等,直到遍历了从栈或静态存储区域中的引用发起的整个链接网路为止。中途移经的每个对象都必须仍处于活动状态。注意对于那些特殊的自引用组,并不会出现前述的问题。由于它们根本找不到,所以会自动当作垃圾处理。
......@@ -33,6 +33,6 @@
正如早先指出的那样,在这里介绍的JVM中,内存是按大块分配的。若分配一个大块头对象,它会获得自己的内存块。严格的“停止和复制”要求在释放旧堆之前,将每个活动的对象从源堆复制到一个新堆,此时会涉及大量的内存转换工作。通过内存块,垃圾收集器通常可利用死块复制对象,就象它进行收集时那样。每个块都有一个生成计数,用于跟踪它是否依然“存活”。通常,只有自上次垃圾收集以来创建的块才会得到压缩;对于其他所有块,如果已从其他某些地方进行了引用,那么生成计数都会溢出。这是许多短期的、临时的对象经常遇到的情况。会周期性地进行一次完整清除工作——大块头的对象仍未复制(只是让它们的生成计数溢出),而那些包含了小对象的块会进行复制和压缩。JVM会监视垃圾收集器的效率,如果由于所有对象都属于长期对象,造成垃圾收集成为浪费时间的一个过程,就会切换到“标记和清除”方案。类似地,JVM会跟踪监视成功的“标记与清除”工作,若内存堆变得越来越“散乱”,就会换回“停止和复制”方案。“自定义”的说法就是从这种行为来的,我们将其最后总结为:“根据情况,自动转换停止和复制/标记和清除这两种模式”。
JVM还采用了其他许多加速方案。其中一个特别重要的涉及装载器以及JIT编译器。若必须装载一个类(通常是我们首次想创建那个类的一个对象时),会找到.class文件,并将那个类的字节码送入内存。此时,一个方法是用JIT编译所有代码,但这样做有两方面的缺点:它会花更多的时间,若与程序的运行时间综合考虑,编译时间还有可能更长;而且它增大了执行文件的长度(字节码比扩展过的JIT代码精简得多),这有可能造成内存页交换,从而显著放慢一个程序的执行速度。另一种替代办法是:除非确有必要,否则不经JIT编译。这样一来,那些根本不会执行的代码就可能永远得不到JIT的编译。
JVM还采用了其他许多加速方案。其中一个特别重要的涉及装载器以及JIT编译器。若必须装载一个类(通常是我们首次想创建那个类的一个对象时),会找到`.class`文件,并将那个类的字节码送入内存。此时,一个方法是用JIT编译所有代码,但这样做有两方面的缺点:它会花更多的时间,若与程序的运行时间综合考虑,编译时间还有可能更长;而且它增大了执行文件的长度(字节码比扩展过的JIT代码精简得多),这有可能造成内存页交换,从而显著放慢一个程序的执行速度。另一种替代办法是:除非确有必要,否则不经JIT编译。这样一来,那些根本不会执行的代码就可能永远得不到JIT的编译。
由于JVM对浏览器来说是外置的,大家可能希望在使用浏览器的时候从一些JVM的速度提高中获得好处。但非常不幸,JVM目前不能与不同的浏览器进行沟通。为发挥一种特定JVM的潜力,要么使用内建了那种JVM的浏览器,要么只有运行独立的Java应用程序。
\ No newline at end of file
由于JVM对浏览器来说是外置的,大家可能希望在使用浏览器的时候从一些JVM的速度提高中获得好处。但非常不幸,JVM目前不能与不同的浏览器进行沟通。为发挥一种特定JVM的潜力,要么使用内建了那种JVM的浏览器,要么只有运行独立的Java应用程序。
# 附录F 推荐读物
《Java in a Nutshell:A Desktop Quick Reference,第2版》
■《Java in a Nutshell:A Desktop Quick Reference,第2版》
作者:David Flanagan
出版社:O'Reilly & Assoc
出版时间:1997
简介:对Java 1.1联机文档的一个简要总结。就个人来说,我更喜欢在线阅览文档,特别是在它们变化得如此快的时候。然而,许多人仍然喜欢印刷出来的文档,这样可以省一些上网费。而且这本书也提供了比联机文档更多的讨论。
■《The Java Class Libraries:An Annotated Reference》
《The Java Class Libraries:An Annotated Reference》
作者:Patrick Chan和Rosanna Lee
出版社:Addison-Wesley
出版时间:1997
简介:作为一种联机参考资源,应向读者提供足够多的说明,使其简单易用。《Thinking in Java》的一名技术审定员说道:“如果我只能有一本Java书,那么肯定选它。”不过我可没有他那么激动。它太大、太贵,而且示例的质量并不能令我满意。但在遇到麻烦的时候,该书还是很有参考价值的。而且与《Java in a Nutshell》相比,它看起来有更大的深度(当然也有更多的文字)。
■《Java Network Programming》
《Java Network Programming》
作者:Elliote Rusty Harold
David Flanagan
出版社:O'Reilly
出版时间:1997
简介:在阅读本书前,我可以说根本不理解Java有关网络的问题。后来,我也发现他的Web站点“Cafe au Lait”是个令人激动的、很人个性的以及经常更新的去处,涉及大量有价值的Java开发资源。由于几乎每天更新,所以在这里能看到与Java有关的大量新闻。站点地址是:http://sunsite.unc.edu/javafaq/。
■《Core Java,第3版》
简介:在阅读本书前,我可以说根本不理解Java有关网络的问题。后来,我也发现他的Web站点“Cafe au Lait”是个令人激动的、很人个性的以及经常更新的去处,涉及大量有价值的Java开发资源。由于几乎每天更新,所以在这里能看到与Java有关的大量新闻。站点地址是:`http://sunsite.unc.edu/javafaq/`
《Core Java,第3版》
作者:Cornel和Horstmann
出版社:Prentice-Hall
出版时间:1997
简介:对于自己碰到的问题,若在《Thinking in Java》里找不到答案,这就是一个很好的参考地点。注意:Java 1.1的版本是《Core Java 1.1 Volume 1-Fundamentals & Core Java 1.1 Volume 2-Advanced Features》
■《JDBC Database Access with Java》
《JDBC Database Access with Java》
作者:Hamilton,Cattell和Fisher
出版社:Addison-Wesley
出版时间:1997
简介:如果对SQL和数据库一无所知,这本书就可以作为一个相当好的起点。它也对API进行了详尽的解释,并提供一个“注释参考。与“Java系列”(由JavaSoft授权的唯一一套丛书)的其他所有书籍一样,这本书的缺点也是进行了过份的渲染,只说Java的好话——在这一系列书籍里找不到任何不利于Java的地方。
■《Java Programming with CORBA》
《Java Programming with CORBA》
作者:Andreas Vogel和Keith Duddy
出版社:Jonh Wiley & Sons
出版时间:1997
简介:针对三种主要的Java ORB(Visbroker,Orbix,Joe),本书分别用大量代码实例进行了详尽的阐述。
■《设计模式》
《设计模式》
作者:Gamma,Helm,Johnson和Vlissides
出版社:Addison-Wesley
出版时间:1995
简介:这是一本发起了编程领域方案革命的经典书籍。
■《UML Toolkit》
《UML Toolkit》
作者:Hans-Erik Eriksson和Magnus Penker
出版社:Jonh Wiley & Sons
出版时间:1997
简介:解释UML以及如何使用它,并提供Java的实际案例供参考。配套CD-ROM包含了Java代码以及Rational Rose的一个删减版本。本书对UML进行了非常出色的描述,并解释了如何用它构建实际的系统。
■《Practical Algorithms for Programmers》
《Practical Algorithms for Programmers》
作者:Binstock和Rex
出版社:Addison-Wesley
出版时间:1995
简介:算法是用C描述的,所以它们很容易就能转换到Java里面。每种算法都有详尽的解释。
\ No newline at end of file
简介:算法是用C描述的,所以它们很容易就能转换到Java里面。每种算法都有详尽的解释。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册