package info.bioz.test;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.*;
import java.util.Date;
import java.text.SimpleDateFormat;
/**
* File: StopWatch.java
* BIOZ.info Copyright (c) 2004
*
* @author Qiang Wang
*/
public class StopWatch extends JFrame {
JButton btnStart,btnStop;
JLabel label;
Timer timer;
public StopWatch() {
label=new JLabel(“00:00:00.000″);
btnStart=new JButton(“start”);
btnStop=new JButton(“stop”);
final int delay=100;
final Date startTime=new Date();
final SimpleDateFormat sdf=new SimpleDateFormat(“HH:mm:ss.SS”);
final Action taskPerformer = new AbstractAction() {
public void actionPerformed(ActionEvent evt) {
Date d=new Date(System.currentTimeMillis()-startTime.getTime()-28800000);
label.setText(sdf.format(d));
label.repaint();
}
};
btnStart.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt) {
startTime.setTime(System.currentTimeMillis());
timer=new Timer(delay, taskPerformer);
timer.start();
}
});
btnStop.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent evt) {
if(timer!=null&&timer.isRunning())
timer.stop();
}
});
Container c=getContentPane();
c.add(label,BorderLayout.NORTH);
c.add(btnStart,BorderLayout.CENTER);
c.add(btnStop,BorderLayout.SOUTH);
}public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
StopWatch window=new StopWatch();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
}
public static void setUIFont (javax.swing.plaf.FontUIResource f){
//
// sets the default font for all Swing components.
// ex.
// setUIFont (new javax.swing.plaf.FontUIResource(“Serif”,Font.ITALIC,12));
//
java.util.Enumeration keys = UIManager.getDefaults().keys();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
Object value = UIManager.get (key);
if (value instanceof javax.swing.plaf.FontUIResource)
UIManager.put (key, f);
}
}
| 用Java编写应用时,有时需要在程序中调用另一个现成的可执行程序或系统命令,这时可以通过组合使用Java提供的Runtime类和Process类的方法实现。下面是一种比较典型的程序模式: |
| … |
| Process process = Runtime.getRuntime().exec(“.\\p.exe”); |
| process.waitfor( ); |
| … |
| 在上面的程序中,第一行的“.\\p.exe”是要执行的程序名,Runtime.getRuntime()返回当前应用程序的Runtime对象,该对象的exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。通过Process可以控制该子进程的执行或获取该子进程的信息。第二条语句的目的等待子进程完成再往下执行。 |
| 但在windows平台上,如果处理不当,有时并不能得到预期的结果。下面是笔者在实际编程中总结的几种需要注意的情况: |
| 1、执行DOS的内部命令 |
| 如果要执行一条DOS内部命令,有两种方法。一种方法是把命令解释器包含在exec()的参数中。例如,执行dir命令,在NT上,可写成exec(“cmd.exe /c dir”),在windows 95/98下,可写成“command.exe /c dir”,其中参数“/c”表示命令执行后关闭Dos立即关闭窗口。另一种方法是,把内部命令放在一个批命令my_dir.bat文件中,在Java程序中写成exec(“my_dir.bat”)。如果仅仅写成exec(“dir”),Java虚拟机则会报运行时错误。前一种方法要保证程序的可移植性,需要在程序中读取运行的操作系统平台,以调用不同的命令解释器。后一种方法则不需要做更多的处理。 |
| 2、打开一个不可执行的文件 |
| 打开一个不可执行的文件,但该文件存在关联的应用程序,则可以有两种方式。 以打开一个word文档a.doc文件为例,Java中可以有以下两种写法: |
| exec(“start .\\a.doc”); |
| exec(” c:\\Program Files\\Microsoft Office\\office\\winword.exe .\\a.doc”); |
| 显然,前一种方法更为简捷方便。 |
| 3、执行一个有标准输出的DOS可执行程序 |
| 在windows平台上,运行被调用程序的DOS窗口在程序执行完毕后往往并不会自动关闭,从而导致Java应用程序阻塞在waitfor( )。导致该现象的一个可能的原因是,该可执行程序的标准输出比较多,而运行窗口的标准输出缓冲区不够大。解决的办法是,利用Java提供的Process 类提供的方法让Java虚拟机截获被调用程序的DOS运行窗口的标准输出,在waitfor()命令之前读出窗口的标准输出缓冲区中的内容。一段典型的程序如下: |
| … |
| String ls_1; |
| Process process = Runtime.getRuntime().exec(“cmd /c dir \\windows”); |
| BufferedReader bufferedReader = new BufferedReader( \ |
| new InputStreamReader(process.getInputStream()); |
| while ( (ls_1=bufferedReader.readLine()) != null) |
| System.out.println(ls_1); |
| process.waitfor( ); |
Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。本文首先简要介绍从Java文件的编译到最终执行的过程,随后对JVM规格描述作一说明。
一.Java源文件的编译、下载、解释和执行
Java应用程序的开发周期包括编译、下载、解释和执行几个部分。Java编译程序将Java源程序翻译为JVM可执行代码—字节码。这一编译过程同 C/C++的编译有些不同。当C编译器编译生成一个对象的代码时,该代码是为在某一特定硬件平台运行而产生的。因此,在编译过程中,编译程序通过查表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。Java编译器却不将对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布局,而是将这些符号引用信息保留在字节码中,由解释器在运行过程中创立内存布局,然后再通过查表来确定一个方法所在的地址。这样就有效的保证了Java的可移植性和安全性。运行JVM字节码的工作是由解释器来完成的。解释执行过程分三部进行:代码的装入、代码的校验和代码的执行。装入代码的工作由”类装载器”(class loader)完成。类装载器负责装入运行一个程序需要的所有代码,这也包括程序代码中的类所继承的类和被其调用的类。当类装载器装入一个类时,该类被放在自己的名字空间中。除了通过符号引用自己名字空间以外的类,类之间没有其他办法可以影响其他类。在本台计算机上的所有类都在同一地址空间内,而所有从外部引进的类,都有一个自己独立的名字空间。这使得本地类通过共享相同的名字空间获得较高的运行效率,同时又保证它们与从外部引进的类不会相互影响。当装入了运行程序需要的所有类后,解释器便可确定整个可执行程序的内存布局。解释器为符号引用同特定的地址空间建立对应关系及查询表。通过在这一阶段确定代码的内存布局,Java很好地解决了由超类改变而使子类崩溃的问题,同时也防止了代码对地址的非法访问。随后,被装入的代码由字节码校验器进行检查。校验器可发现操作数栈溢出,非法数据类型转化等多种错误。通过校验后,代码便开始执行了。
Java字节码的执行有两种方式:
1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。
2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。
通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。
二.JVM规格描述
JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提很好的灵活性,同时也确保Java代码可在符合该规范的任何系统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。 JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是:
JVM指令系统
JVM寄存器
JVM栈结构
JVM碎片回收堆
JVM存储区
2.1 JVM指令系统 JVM指令系统同其他计算机的指令系统极其相似。Java指令也是由操作码和操作数两部分组成。操作码为8位二进制数,操作数进紧随在操作码的后面,其长度根据需要而不同。操作码用于指定一条指令操作的性质(在这里我们采用汇编符号的形式进行说明),如iload表示从存储器中装入一个整数,anewarray表示为一个新数组分配空间,iand表示两个整数的”与”, ret用于流程控制,表示从对某一方法的调用中返回。当长度大于8位时,操作数被分为两个以上字节存放。JVM采用了”big endian”的编码方式来处理这种情况,即高位bits存放在低字节中。这同 Motorola及其他的RISC CPU采用的编码方式是一致的,而与Intel采用的”little endian “的编码方式即低位bits存放在低位字节的方法不同。 Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多先程系统的指令。Java的8位操作码的长度使得JVM最多有256种指令,目前已使用了160多种操作码。
2.2 JVM指令系统所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组。如果虚拟机定义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用常规存储器模拟寄存器,这反而会降低虚拟机的效率。针对这种情况,JVM只设置了4个最为常用的寄存器。它们是:
pc程序计数器
optop操作数栈顶指针
frame当前执行环境指针
vars指向当前执行环境中第一个局部变量的指针
所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。
2.3 JVM栈结构 作为基于栈结构的计算机,Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:
局部变量
执行环境
操作数
栈局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。 执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。操作数栈用于存储运算所需操作数及运算的结果。
2.4 JVM碎片回收堆 Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。在Java语言中,除了new语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由Java运行系统承担的。这允许Java运行系统的设计者自己决定碎片回收的方法。在SUN公司开发的Java解释器和Hot Java环境中,碎片回收用后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。
2.5 JVM存储区 JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。对于这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。
JVM是为Java字节码定义的一种独立于具体平台的规格描述,是Java平台独立性的基础。目前的JVM还存在一些限制和不足,有待于进一步的完善,但无论如何,JVM的思想是成功的。
对比分析:如果把Java原程序想象成我们的C++原程序,Java原程序编译后生成的字节码就相当于C++原程序编译后的80×86的机器码(二进制程序文件),JVM虚拟机相当于80×86计算机系统,Java解释器相当于80×86CPU。在80×86CPU上运行的是机器码,在Java解释器上运行的是Java字节码。
Java解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的。Java解释器实际上就是特定的平台下的一个应用程序。只要实现了特定平台下的解释器程序,Java字节码就能通过解释器程序在该平台下运行,这是Java跨平台的根本。当前,并不是在所有的平台下都有相应Java解释器程序,这也是Java并不能在所有的平台下都能运行的原因,它只能在已实现了Java解释器程序的平台下运行。
From: http://www.huachu.com.cn/2003/java.htm
理论上,理论和实际并没有区别;实际上,它们是有区别的。
In theory, there is no difference between theory and practice. But, in practice, there is.
- Jan L.A. van de Snepscheut
| 1 一般技术(General Techniques) |
所有Java对象都通过object reference被访问。常见的一个误解是Java以by reference方式传递自变量。事实上所有自变量都以by value方式传递。
实践2:对不变的data和object reference使用final 3
为了让data或object reference成为不变量(常数),请使用final。注意,final仅仅令object reference自身成为不变量,并不限制它所指对象的改变。
实践3:缺省情况下所有non-static函数都可被覆写 6
缺省情况下,所有non-static函数都可以被subclass覆写。但如果加上关键词final,便可防止被subclass覆写。
实践4:在arrays和Vectors之间慎重选择 7
arrays和vectors是常见的容器类(storage classes)。选用它们之前应该先了解它们的功用和特性。
实践5:多态(polymorphism)优于instanceof 11
instanceof的许多用途可以因为改用多态而消失。使用多态,代码将更清晰、更易于扩展和维护。
实践6:必要时才使用instanceof 15
有时我们无法回避使用instanceof。我们应该了解什么情况下必须使用它。
实践7:一旦不再需要object references,就将它设为null 18
不要忽视内存可能带来的问题。尽管有了垃圾回收机制(garbage collection),你仍然需要关注你的代码如何运用内存。如果能够领悟垃圾回收机制和内存运用细节,你就能够更好地知道何时应该将object references设为null,那将导致高效的代码。
| 2 对象与相等性(Objects and Equality) |
实践8:区分reference type和primitive type 25
Java是面向对象语言,但其操控的东西并非都是对象(objects)。理解reference type和primitive types之间的差异,及它们在JVM中的表述(representation),会使你在运用它们时得以做出明智的选择。
实践9:区分 == 和equals() 29
== 用来测试基本型别的相等性,亦可判定两个object references是否指向同一个对象。但若要测试values(值)或semantic(语义)相等,应使用equals()。
实践10:不要倚赖equals()的缺省实现 33
不要不假思索地认定一个class总是会正确实现出equals()。此外,java.lang.Object提供的equals()大多数时候并非进行你想要的比较。
实践11:实现equals()时必须深思熟虑 43
如果某个class所生的两个对象「即使不占用相同的内存空间,也被视为逻辑上相等」,那么就该为这个class提供一个equals()。
实践12:实现equals()时优先考虑使用getClass() 44
实现equals()时请优先考虑采用getClass()。毕竟,「隶属同一个class下的对象才得被视为相等」是正确实现equals()的一个简明方案。
实践13:调用super.equals()以唤起base class的相关行为 47
任何base class(除了java.lang.Object)如果实现equals(),其derived class都应该调用super.equals()。
实践14:在equals()函数中谨慎使用instanceof 51
惟有当你考虑允许「一个derived class对象可以相等于其base class对象」时,才在equals()中使用instanceof。使用这项技术前请先弄清楚其影响。
实践15:实现equals()时需遵循某些规则 60
撰写equals()并非那么直观浅白。如果想要恰当地实现出equals(),请遵循某些规则。
| 3 异常处理(Exception Handling) |
实践16:认识「异常控制流」(exception control flow)机制 62
让自己晓谙异常控制流程细节。了解这些细微之处有助于你回避问题。
实践17:绝对不可轻忽异常(Never ignore an Exceptions) 65
一旦异常出现却没有被捕获,抛出异常的那个线程(thread)就会中止运行。是的,异常意味错误,永远不要忽略它。
实践18:千万不要遮掩异常(Never hide an Exceptions) 68
如果处理异常期间又从catch或finally区段抛出异常,原先的异常会因而被隐蔽起来。一旦发生这样的事情,就会丢失错误信息。你应当撰写专门负责处理这种情形的代码,将所有异常回传给调用者。
实践19:明察throws子句的缺点 73
将一个异常加入某函数的throws子句,会影响该函数的所有调用者。
实践20:细致而全面地理解throws子句 74
任何函数的throws子句应当列出它所传播的所有异常,包括衍生异常型别(derived exception types)。
实践21:使用finally避免资源泄漏(resource leaks) 77
不要忽视内存以外的资源。垃圾回收机制不会替你释放它们。请使用finally确保内存以外的资源被释放。
实践22:不要从try区段中返回 79
不要从try区段中发出return语句,因为这个函数未必会立即从那儿返回。如果存在finally区段,它就会被运行起来并可能改变回传值。
实践23:将try/catch区段置于循环(loop)之外 81
撰写含有异常处理的循环时,请将try和catch区段置于循环外部。在某些实现版本上,这会产生更快的运行码。
实践24:不要将异常(exceptions)用于流程控制 84
请将异常用于预期行为之外的情况。不要以异常来控制流程,请采用标准的语言流程构件(flow constructs),这样的流程表达会更清晰更高效。
实践25:不要每逢出错就使用异常(exceptions) 85
只有面对程序行为可能出乎预料的情境下才使用异常。「预期中的行为」应使用返回码(return codes)来处理。
实践26:在构造函数(constructors)中抛出异常 86
尽管构造函数并非函数(method),因而不能回传一个值,但构造函数有可能失败。如果它们失败了,请抛出一个异常。
实践27:抛出异常之前先将对象恢复为有效状态(valid state) 88
抛出异常很容易,困难的是「将异常所引发的伤害减到最小」。抛出异常之前,应确保「如果异常被处理好,流程再次进入抛出异常的那个函数中,该函数可以成功完成」。
| 4 性能(Performance) |
实践28:先把焦点放在设计、数据结构和算法身上 99
给Java带来最大性能提升的办法就是:在设计和算法中使用与语言无关的技术。因此,首先请将你的精力集中于这些上面。
实践29:不要倚赖编译期(compile-time)优化技术 101
由Java编译器生成的码,通常不会比你自己撰写的更好。别指望编译器能够多么优化你的源码。
实践30:理解运行期(runtime)代码优化技术 105
Java对性能的大部分努力都围绕着「运行期优化」展开。这种作法有利有弊。
实践31:如欲进行字符串接合,StringBuffer优于String 107
对于字符串接合,StringBuffer要比String快许多倍。
实践32:将对象的创建成本(creation cost)降至最小 109
在许多面向对象系统中,「创建对象」意味高昂的成本。了解成本所在,以及了解「加速对象创建速度」的技术,都可以导致更快速的程序。
实践33:慎防未用上的对象(unused objects) 114
非必要别产生对象,否则会减慢你的程序速度。
实践34:将同步(synchronization)减至最低 116
宣告synchronized函数或synchronized区段,会显著降低性能。应该只在对象有所需要时才使用同步机制(synchronization)。
实践35:尽可能使用stack变量 122
stack变量为JVM提供了更高效的byte code指令序列。所以在循环内重复访问static变量或instance变量时,应当将它们暂时存储于stack变量中,以便获得更快的运行速度。
实践36:使用static、final和private函数以促成inlining 126
以函数本体(method body)替换函数调用(method call),会导致更快速的程序。如果要令函数为inline,必须先宣告它们为static、final或private。
实践37:instance变量的初始化一次就好 127
由于所有static变量和instance变量都会自动获得缺省值,所以不必重新将它们设为缺省值。
实践38:使用基本型别(primitive types)使代码更快更小 130
使用基本型别,比使用其外覆类(wrapper),产生的代码又小又快。
实践39:不要使用Enumeration或Iterator来遍历Vector 135
遍历Vector时,请使用get()函数而非Enumeration或Iterator。这样做会导致更少的函数调用,意味程序速度更快。
实践40:使用System.arraycopy() 来复制arrays 136
请使用System.arraycopy() 来复制arrays。那是个本机(native)函数,速度最快。
实践41:优先使用array,然后才考虑Vector和ArrayList 138
如果可能,就使用array。如果你需要Vector的功能但不需要它的同步特性,可改用ArrayList。
实践42:尽可能复用(reuse)对象 141
复用现有对象,几乎总是比创建新对象更划算。
实践43:使用缓式评估(延迟求值,lazy evaluation) 144
如果某个成本高贵的计算并非一定必要,就尽量少做。请使用「缓式评估」
(lazy evaluation,延迟求值)技术避免那些永远不需要的工作。
实践44:以手工方式将代码优化 151
由于Java编译器在优化方面的作为甚少,为了生成最佳byte code,请以手工方式将你的源码优化。
实践45:编译为本机代码(Compile to native code) 159
编译为本机代码,通常可以获得运行速度更快的代码。但你却因此必须在各种不同的本机方案(native solution)中取舍。
| 5 多线程(Multithreading) |
实践46:面对instance函数,synchronized锁定的是
对象(object)而非函数(method)或代码(code) 162
关键词synchronized锁定的是对象,而非函数或代码。一个函数或程序
区段被宣告为synchronized,并不意味同一时刻只能由一个线程运行它。
实践47:弄清楚synchronized statics函数与synchronized instance函数
之间的差异 166
两个函数被宣告为synchronized,并不就意味它们是「多线程安全」(thread-safe)。对instance函数或object reference同步化,与对static函数或class literal(字面常数)同步化相比,得到的lock全然不同。
实践48:以「private数据 + 相应的访问函数(accessor)」替换
「public/protected数据」 170
如果没有适当保护你的数据,用户便有机会绕过你的同步机制。
实践49:避免无谓的同步控制 173
一般情况下请不要同步化所有函数。同步化不仅造成程序缓慢,并且丧失了
并发(concurrency)的可能。请采用「单对象多锁」技术以允许更多并发动作。
实践50:访问共享变量时请使用synchronized或volatile 176
不可切割(原子化, atomic)操作并非意味「多线程安全」。JVM实现品被允许在私有内存中保留变量的工作副本。这可能会产生陈旧数值(stale values)。为避免这个问题,请使用同步化机制或将变量宣告为volatile。
实践51:在单一操作(single operation)中锁定所有用到的对象 180
同步化某一函数,并不一定就会使其成为「多线程安全」。如果synchronized函数操控着多个对象,而它们并不都是此函数所属class的private instance data,那么你必须对这些对象自身也进行同步化。
实践52:以固定而全局性的顺序取得多个locks(机锁)
以避免死锁(deadlock) 181
当你同步化多个对象,请以固定、全局性的顺序获得locks,以避免死锁。
实践53:优先使用notifyAll()而非notify() 185
notify()只唤醒一个线程。要想唤醒多个线程,请使用notifyAll()。
实践54:针对wait()和notifyAll()使用旋锁(spin locks) 187
当你等待条件变量(condition variables)时,请总是使用旋锁(spin locks)确保正确结果。
实践55:使用wait()和notifyAll()替换轮询循环(polling loops) 191
将所有polling loops替换为使用wait()、notify()和notifyAll()的spin locks(旋锁)。spin locks直观而高效,polling loops则慢很多倍。
实践56:不要对locked object(上锁对象)之object reference重新赋值 194
当一个对象被锁定,有可能其他线程会因同一个object lock而受阻(blocked)。假如你对上锁对象的object reference重新赋值,其他线程内悬而未决的那些locks将不再有意义。
实践57:不要调用stop()或suspend() 197
不要调用stop()或suspend(),因为它们可能导致数据内部混乱,甚至引发死锁(deadlock)。
实践58:通过线程(threads)之间的协作来中止线程 198
你不应该调用stop()。如欲安全地停止线程,必须要求它们相互协作,才能姿态优雅地中止。
| 6 类与界面(Classes and Interfaces) |
实践59:运用interfaces支持多重继承(multiple inheritance) 201
当你想要支持interface的单一继承或多重继承,或想要实现一个标识型(marker)interface时,请使用interfaces。
实践60:避免interfaces中的函数发生冲突 206
没有任何办法能够阻止两个interfaces使用同名的常数和函数。为了避免可能的冲突,应当小心命名常数和函数。
实践61:如需提供部分实现(partial implementation),
请使用abstract classes(抽象类) 209
使用abstract class来为一个class提供部分实现,这些实现很可能对derived class是共通的(都被需要的)。
实践62:区分interface、abstract class和concrete class 212
一旦正确理解interface、abstract class和concrete class的差异,你就可以在设计和撰码时做出正确选择。
实践63:审慎地定义和实现immutable classes(不可变类) 213
如果你希望对象内容永远不被改动,请使用不可变对象(immutable object)。这种对象自动拥有「多线程安全性」(thread safety)。
实践64:欲传递或接收mutable objects(可变对象)之object references时,
请实施clone() 215
为了保证immutable objects,你必须在传入和回传它们时对它们施行clone()。
实践65:使用继承(inheritance)或委托(delegation)来定义
immutable classes(不可变类) 226
使用immutable interface、common interface或base class,或是immutable delegation classes,来定义immutable classes(不可变类)。
实践66:实现clone()时记得调用super.clone() 233
当你实现了一个clone(),总是应该调用super.clone()以确保产生正确的对象。
实践67:别只倚赖finalize()清理non-memory(内存以外)的资源 235
你不能保证finalize()是否被调用,以及何时被调用。因此,请专门实现一个public函数来释放内存以外的资源。
实践68:在构造函数内调用non-final函数时要当心 238
如果一个non-final函数被某个derived class覆写,在构造函数中调用这个函数可能会导致不可预期的结果。
有时一个程序因为有大量的线程在运行而极难调试。在这种情况下,下面的这个类可能会派上用场
public class Probe extends Thread {
public Probe() {}
public void run() {
while(true) {
Thread[] x = new Thread[100];
Thread.enumerate(x);
for(int i=0; i<100; i++) {
Thread t = x[i];
if(t == null)
break;
else
System.out.println(t.getName() + "\t" + t.getPriority()
+ "\t" + t.isAlive() + "\t" + t.isDaemon());
}
}
}
}
import java.io.FilenameFilter;
import java.io.File;
class HTMLFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
return (name.endsWith(".html"));
}
}
class Main {
public static void main (String[] args) {
String dir = ".";
if (args.length == 1)
dir = args[0];
File f1 = new File(dir);
int i;
String[] ls;
FilenameFilter filter = new HTMLFilter();
System.out.println("HTML Files: " );
for (ls = f1.list(filter), i = 0;
ls != null && i < ls.length;
System.out.println("\t" + ls[i++]));
}
}
//Producer产生一个数字而Consumer消费这个数字
public class Producer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println(“Producer #” + this.number
+ ” put: ” + i);
try {
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) { }
}
}
}
public class CubbyHole {
private int contents;
private boolean available = false;
public synchronized int get() {
while (available == false) {
try {
wait();
} catch (InterruptedException e) { }
}
available = false;
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait();
} catch (InterruptedException e) { }
}
contents = value;
available = true;
notifyAll();
}
}
public class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println(“Consumer #” + this.number
+ ” got: ” + value);
}
}
}
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
c1.start();
}
}
SUN说输出的结果应该是如下形式,但是在我的机器上却不是这样的,做了一些改动才正确,有兴趣的朋友可以运行一下看看结果,欢迎和我讨论一下!
Producer #1 put: 0
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9
问题:
在使用 Struts 的 I18N 时,jsp页面无法正确显示中文,要选择编码为“简体中文”才能正确显示。![]()
- 首先,按照网上说的方法用 native2ascii 将中文的 properties 文件转为 \uxxxx 形式的编码。但是这样显示出来的是?,即使选择浏览器编码为中文也不行。失败
- 直接使用中文的properties文件,可以正确显示中文;不过需要手动调浏览器的编码,否则显示为乱码。失败
- 设置页面的contentType=”text/html; charset=<bean:message key=”html.charset” />”,其中html.charset是在properties文件中设置的一个key,这样就可以根据浏览器的locale选择编码。但是问题依旧。失败
- 最终发现是jsp页面的问题,因为是个测试页,没有使用<% page language=”java” contentType=”text/html; charset=GBK” %>。现在解决了需要手动选择编码的问题。但是,因为期望的是根据客户端的locale选择编码类型,而<% %>里不能使用<bean:message key=”html.charset” />,所以问题还不能算解决。失败
- 设置jsp编码为UTF-8,似乎能够正确显示了;但是在从新启动tomcat之后,开始出现乱码。选择字符集也无法解决问题。失败
- 现在再使用 native2ascii 将properties文件转为 \uxxxx 形式,问题解决。

