当前页面: 开发资料首页 → JSP 专题 → Java 库的建立方法及其实例
摘要: Java 库的建立方法及其实例
作者 ariesram
电子邮件地址 ariesram@linuxaid.com.cn, 或 ariesram@may10.ca
本文及本人所有文章均收集在bambi.may10.ca/~ariesram/articles/中。
本文授权给www.linuxaid.com.cn。
正文:
任何一种面向对象语言都有它的库。任何一种面向对象的语言也都离不开库的支持。用我们熟悉的 面向对象语言为例子,C++有STL,Java有API函数,具体到开发工具,Visual C++提供了MFC, Borland C++提供了OWL。也有很多第三方提供的库。我们在开发应用程序的时候,也发觉我们也 许需要某些特定的库来完成特定的功能。那么,如何编写自己的库呢?
利用Java的面向对象特性,如封装,继承,和一些设计模式,我们可以用标准的方法来建立自己的 库。需要明白的一点:在你需要完成某个功能的时候,不要用专有的、特定的方法去编写代码,而 要全盘考虑,用通用的方法来完成,这样,在积累了一定数量的库以后,你就能重用这些库来完成 新的功能,而不用每回都重头编写代码。这也是面向对象语言提供给我们的好处。也可以用J2EE的 规范为例子,J2EE提供了一个CBT(Component Based Transaction),所有的组件都尊崇J2EE规范,在 CBT中运行,这样,编写开发并且重用标准的通用的组件库,可以缩短开发周期节约成本,并且可 以在任何符合J2EE规范的应用程序服务器(APPLICATION SERVER)中运行,并且可以继承,扩展已 有的组件库完成新的任务或者适应新的变化。
在本文中,我将先讨论如何建立自己的库,需要根据哪些标准,然后给出一个简单的例子。在第二 部分中,我将通过一个功能比较完善的库来做进一步的讨论。
什么是库?库是一个可以重用的组件,它采用通用的设计,完成通用的任务,可以节约开发者的时 间,缩短开发周期节约开发成本。一个设计完善的库,并不只是为了完成某一个特定的任务,而是 可以完成各种不同的任务。设计一个库是困难的。写一个算法并不难,但是设计库的时候需要一种 比较好的结构,它能够被用在各种需要的环境下,完成各种不同的任务,但是还不能影响使用它的 程序代码结构。
为什么要重用代码?重头开发一个新的软件,工作量是非常巨大的,不论你用什么工具什么语言。 而代码重用能够节约大部分时间,而把时间花在新的功能的开发上。从一定的意义上来说,写一个 新的软件是利用了现有的代码,重新拼装以实现新的功能。从另外一个角度上来讲,即使你没有打 算把你写的代码变成一个通用的库并分发给其他人使用,从设计的角度来讲,采用一种全盘的通用 的设计方法也能让你对所要完成的任务有更好的理解,并且优化你的设计过程,从而优化你的代码 结构。
采用开发库并且让别人来使用它的方式,能够帮助你在使用它的时候发现它的设计上的缺陷或者代 码中的错误,并帮助你改正它。比方说,你写了一个库让别人来使用,你不得不考虑通用的设计, 因为你并不能预见别人将在什么环境下使用和使用的目的。在其他人使用你的库的过程中,可能会 遇到一些问题,有的可能是你的文档写得不够清楚明白,有的也可能是你程序上的错误,也有可能 是使用者觉得在结构上使用起来不方便或者不正确。那么你可以继续作一些修改工作,在保持结构 和接口不变化的情况下,做一些调整。
在设计库的时候,你需要以一个使用者的眼光来看问题,考虑如何设计和实现它。你需要明白,
1、需要解决的问题是什么?需要达到一个什么目的?
2、使用者关心的问题是什么?使用者需要得到一个什么结果?
3、使用者不需要关心的问题是什么?什么细节是可以对使用者隐藏的?
下面,我们用一个简单的例子来说明如何设计和实现一个有用处的库。
设计一个网络服务程序,我们需要考虑几点:
1、监听一个端口
2、接受连接
3、读取或者写入连接的流
4、处理输入的数据,并且返回一个结果
对于我们将要实现的库来说,需要完成的是前三点,而最后一点我们留给使用者去实现,这也是使 用者需要完成和关心的地方。
库的主要类叫做Server, 测试的类叫做EchoServer. EchoServer实现了一个简单的服务,从客户端读 取数据,并且返回同样的数据。
设计原则一:封装
一个好的库必须是一个紧凑的关系紧密的整体,而不是一个分散的关系松散的对象的集合。
package是Java提供的一种类库的封装机制。一个package是一个Java类文件的集合,存放在同一个目 录中。package有专有的名字空间。
专有的名字空间的一个好处是,你不用担心名称的冲突。因为,如果你的类的名称和别人的类的名 称冲突,但是他们不在同一个package中,利用这一点可以避免名字的冲突。
每一个package都有一个字符串来代表,比如java.lang, 或者javax.swing.plaf.basic.实际上每一个类的 全名都是由package的名字加上类的名字来代表的,这样就避免了名字的冲突,比 如,java.lang.Object或者javax.swing.plaf.basic.BasicMenuBarUI.
注意,有一个特殊的package叫做default package。如果你不声明你的类属于任何一个package,那么 它就被假定属于default package.
每一个package的名字都对应一个目录。比如,java.lang.Object 存放在java/lang/Object.java中,每一 个.对应一个/. default package存放的目录是当前目录。
声明一个package.
// Server.java
package mylib;
public class Server implements Runnable
{
// ...
如果有import语句,必须放在package语句的后面。
当然你也可以引入别的package. 例如:
import mylib.Server;
// ...
Server server = new Server( portNum );
Java允许你决定package中的哪些类对外部是可见的。public类可以被包外的代码使用,而private类 则不行。
比如,让Server类能被外部的代码使用:
// Server.java
package mylib;
import java.io.*;
import java.net.*;
public class Server implements Runnable
{
如果你不想让类被外部的代码使用,可以用缺省的属性,去掉public. 例如:
// Reporter.java
package mylib;
class Reporter implements Runnable
{
设计原则二:继承
在我们的例子中,Server是主要的类。如果你看这个类的代码,就能看到,它本身其实什么也不 做。主循环用来监听连接。当连接建立以后,它把处理连接的任务交给一个叫做handleConnection() 的函数。
// subclass must supply an implementation
abstract public void handleConnection( Socket s );
因为没有实现这一函数,所以这个类被声明为abstract,使用者必须实现这个函数。
// This is called by the Server class when a connection
// comes in. "in" and "out" come from the incoming socket
// connection
public void handleConnection( Socket socket ) {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// just copy the input to the output
while (true)
out.write( in.read() );
} catch( IOException ie ) {
System.out.println( ie );
}
}
可以说,这一继承的过程叫做定制。因为在Server类中,并没有定义该函数的动作,而是把这个定 义的过程留给使用者,让他们来完成所需要的特定的功能。
另外一个定制函数:cleanUp().
在设计类的时候,往往你能考虑到使用者需要的功能,例如上面的handleConnection().但是,也需要 考虑另外一种定制,例如在这里,在Server退出后台运行方式的时候,调用了这个cleanUp()函数, 在Server类中的实现为空,什么都不做,这把机会留给使用者,使用者可以用这个函数来做一些清 除工作,这种函数也可以称之为"钩子"。
设计原则三:调试
没有人能够做到写出一个绝对完美的程序,没有任何的错误。所以,调试是不可缺少的。有时候, 使用者可能会遇到一个问题,从而需要知道在库的代码中发生了什么问题。这个错误可能是库代码 的问题,也可能是使用者的代码在库代码中引起的问题。
如果你提供了库的源代码,使用者可以用debugger来调试错误。但是,你不能完全依赖于调试器。 在库代码中加入打印调试信息的语句,是一个好习惯。它可以帮助使用者明白,什么地方发生了错 误。
下面的例子说明了这一技术。使用者的代码使用Server.setDebugStream(),指定一个PrintStream对 象。然后,调试信息就被输出到这个流中。
// set this to a print stream if you want debug info
// sent to it; otherwise, leave it null
static private PrintStream debugStream;
// call this to send the debugging output somewhere
static public void setDebugStream( PrintStream ps ) {
debugStream = ps;
}
当使用者使用了调试的流的时候,你的库代码可以打印错误:
// send debug info to the print stream, if there is one
static public void debug( String s ) {
if (debugStream != null)
debugStream.println( s );
}
下面,来完整的看一看这个具体的例子:
EchoServer
// $Id$
import java.io.*;
import java.net.*;
import mylib.*;
public class EchoServer extends Server
{
public EchoServer( int port ) {
// The superclass knows what to do with the port number, we
// don't have to care about it
super( port );
}
// This is called by the Server class when a connection
// comes in. "in" and "out" come from the incoming socket
// connection
public void handleConnection( Socket socket ) {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// just copy the input to the output
while (true)
out.write( in.read() );
} catch( IOException ie ) {
System.out.println( ie );
}
}
protected void cleanUp() {
System.out.println( "Cleaning up" );
}
static public void main( String args[] ) throws Exception {
// Grab the port number from the command-line
int port = Integer.parseInt( args[0] );
// Have debugging info sent to standard error stream
Server.setDebugStream( System.err );
// Create the server, and it's up and running
new EchoServer( port );
}
}
mylib.Server
// $Id$
package mylib;
import java.io.*;
import java.net.*;
abstract public class Server implements Runnable
{
// the port we'll be listening on
private int port;
// how many connections we've handled
int numConnections;
// the Reporter that's reporting on this Server
private Reporter reporter;
// set this to true to tell the thread to stop accepting
// connections
private boolean mustQuit = false;
public Server( int port ) {
// remember the port number so the thread can
// listen on it
this.port = port;
// the constructor starts a background thread
new Thread( this ).start();
// and start a reporter
reporter = new Reporter( this );
}
// this is our background thread
public void run() {
ServerSocket ss = null;
try {
// get ready to listen
ss = new ServerSocket( port );
while( !mustQuit ) {
// give out some debugging info
debug( "Listening on "+port );
// wait for an incoming connection
Socket s = ss.accept();
// record that we got another connection
numConnections++;
// more debugging info
debug( "Got connection on "+s );
// process the connection -- this is implemented
// by the subclass
handleConnection( s );
}
} catch( IOException ie ) {
debug( ie.toString() );
}
debug( "Shutting down "+ss );
cleanUp();
}
// the default implementation does nothing
abstract public void handleConnection( Socket s );
// tell the thread to stop accepting connections
public void close() {
mustQuit = true;
reporter.close();
}
// Put any last-minute clean-up stuff in here
protected void cleanUp() {
}
// everything below provides a simple debug system for
// this package
// set this to a print stream if you want debug info
// sent to it; otherwise, leave it null
static private PrintStream debugStream;
// we have two versions of this ...
static public void setDebugStream( PrintStream ps ) {
debugStream = ps;
}
// ... just for convenience
static public void setDebugStream( OutputStream out ) {
debugStream = new PrintStream( out );
}
// send debug info to the print stream, if there is one
static public void debug( String s ) {
if (debugStream != null)
debugStream.println( s );
}
}
mylib.Reporter
// $Id$
package mylib;
class Reporter implements Runnable
{
// the Server we are reporting on
private Server server;
// our background thread
private Thread thread;
// set this to true to tell the thread to stop accepting
// connections
private boolean mustQuit = false;
Reporter( Server server ) {
this.server = server;
// create a background thread
thread = new Thread( this );
thread.start();
}
public void run() {
while (!mustQuit) {
// do the reporting
Server.debug( "server has had "+server.numConnections+" connections" );
// then pause a while
try {
Thread.sleep( 5000 );
} catch( InterruptedException ie ) {}
}
}
// tell the background thread to quit
public void close() {
mustQuit = true;
}
}
下面,我们以一个具体的有实际用处的类库来进一步讨论库的设计方法。这个库是jregex. (jregex.sourceforge.net)。这是一个用Java实现的兼容Perl 5.6的正则表达式的库。有很多这样的库, 比如gnu.regexp,com.stevesoft.pat, 还有J2SE SDK 1.4中新增加的regex.为什么选用jregex呢?是因为 它是目前源代码公开的regex库中兼容Perl 5.6的正则表达式,而且刚刚更新过源代码,并且是稳定 版,另外一个就是,它的内核算法选用了NFA(Not Finite Automata)。
要了解一个包,在看源代码之前先应该看的就是它的API文档。那么,看文档的第一步应该看什么 呢?当然是树形结构。
Class Hierarchy
class java.lang.Object
class jregex.Matcher (implements jregex.MatchResult)
class jregex.Optimizer
class jregex.util.io.PathPattern
class jregex.Pattern (implements jregex.REFlags, java.io.Serializable)
class jregex.PerlSubstitution (implements jregex.Substitution)
class jregex.Replacer
class jregex.RETokenizer (implements java.util.Enumeration)
class java.lang.Throwable (implements java.io.Serializable)
class java.lang.Exception
class java.lang.RuntimeException
class java.lang.IllegalArgumentException
class jregex.PatternSyntaxException
class jregex.util.io.WildcardFilter (implements java.io.FilenameFilter)
Interface Hierarchy
interface jregex.MatchIterator
interface jregex.MatchResult
interface jregex.REFlags
interface jregex.Substitution
interface jregex.TextBuffer
interface jregex.Replacer.WriterWrap
--------------------------------------------------------------------------------
在一个正则表达式中,我们知道有两个元素很重要,第一个就是Pattern(模式), 第二个是Matcher(匹 配结果字符串)。在jregex中,Pattern类实现了jregex.REFlags interface, 和java.io.Serializable。先来看 看jregex.REFlags的说明。jregex.REFlags定义了一些静态的常量,看起来是一些标志。Pattern实现了 jregex.REFlags, 也就是说,Pattern类中包含了这些静态的常量。
下一步,我们看看Pattern的API说明:
Pattern是一个预编译好的正则表达式的表示。要匹配一个正则表达式,先创建一个Pattern实例:
Pattern p=new Pattern(myExpr);
然后取得Matcher的实例
Matcher matcher=p.matcher(myText);
Matcher的实例是一个自动的匹配和搜索的对象。它提供如下方法:
搜索匹配结果: matcher.find() or matcher.findAll();
监测是否全文匹配 : matcher.matches();
监测是否匹配开头 : matcher.isStart();
带选项的查找 : matcher.find(int options)
标志
标志(参考REFlags)改变了在预编译的时候正则表达式符号的意义。这些标志是:
REFlags.IGNORE_CASE - 忽略大小写
REFlags.MULTILINE - 用^和$来表示一行文本的开头和结尾
REFlags.DOTALL - 用.来表示回车换行
REFlags.IGNORE_SPACES - 忽略空格
REFlags.UNICODE - 使用UNICODE, 即w, d不再被解释为正则表达式的意义,而是被解释为 UNICODE.
REFlags.XML_SCHEMA - 使用XML语义。
线程
Pattern是线程安全的。也就是说,你可以在不同的线程中使用同一个Pattern的实例。
在API函数说明中,我们还能看到Pattern类的public方法。这一点将在下面有用处。先来看看:
构造函数
Pattern(java.lang.String regex)
Compiles an expression with default flags.
Pattern(java.lang.String regex, int flags)
Compiles a regular expression using REFlags.
Pattern(java.lang.String regex, java.lang.String flags)
Compiles a regular expression using Perl5-style flags.
方法
int groupCount()
How many capturing groups this expression includes?
java.lang.Integer groupId(java.lang.String name)
Get numeric id for a group name.
Matcher matcher()
Returns a targetless matcher.
Matcher matcher(char[] data, int start, int end)
Returns a matcher for a specified region.
Matcher matcher(MatchResult res, int groupId)
Returns a matcher for a match result (in a performance-friendly way).
Matcher matcher(MatchResult res, java.lang.String groupName)
Just as above, yet with symbolic group name.
Matcher matcher(java.io.Reader text, int length)
Returns a matcher taking a text stream as target.
Matcher matcher(java.lang.String s)
Returns a matcher for a specified string.
Replacer replacer(java.lang.String expr)
Returns a replacer of a pattern by specified perl-like expression.
Replacer replacer(Substitution model)
Returns a replacer will substitute all occurences of a pattern through applying a user-defined substitution model.
RETokenizer tokenizer(char[] data, int off, int len)
Tokenizes a specified region by an occurences of the pattern.
RETokenizer tokenizer(java.io.Reader in, int length)
Tokenizes a specified region by an occurences of the pattern.
RETokenizer tokenizer(java.lang.String text)
Tokenizes a text by an occurences of the pattern.
java.lang.String toString_d()
Returns a less or more readable representation of a bytecode for the pattern.
java.lang.String toString()
接下来,我们来看看Pattern类的内容。这里有两种方法,一种是直接阅读源代码,另外一种是先用 工具分析一下Pattern类的内容。这里,我采用第二种方法,用javap来看类的内容。
[games]$javap -classpath .. -private jregex.Pattern
Compiled from jregex/Pattern.java
public class jregex.Pattern extends java.lang.Object implements java.io.Serializable, jregex.REFlags {
java.lang.String stringRepr;
jregex.Term root;
jregex.Term root0;
int memregs;
int counters;
int lookaheads;
java.util.Hashtable namedGroupMap;
private jregex.Pattern() throws jregex.PatternSyntaxException;
public jregex.Pattern(java.lang.String) throws jregex.PatternSyntaxException;
public jregex.Pattern(java.lang.String,java.lang.String) throws jregex.PatternSyntaxException;
public jregex.Pattern(java.lang.String,int) throws jregex.PatternSyntaxException;
private void compile(java.lang.String, int) throws jregex.PatternSyntaxException;
public int groupCount();
public java.lang.Integer groupId(java.lang.String);
public jregex.Matcher matcher();
public jregex.Matcher matcher(java.lang.String);
public jregex.Matcher matcher(char[], int, int);
public jregex.Matcher matcher(jregex.MatchResult, int);
public jregex.Matcher matcher(jregex.MatchResult, java.lang.String);
public jregex.Matcher matcher(java.io.Reader, int) throws java.io.IOException;
public jregex.Replacer replacer(java.lang.String);
public jregex.Replacer replacer(jregex.Substitution);
public jregex.RETokenizer tokenizer(java.lang.String);
public jregex.RETokenizer tokenizer(char[], int, int);
public jregex.RETokenizer tokenizer(java.io.Reader, int) throws java.io.IOException;
public java.lang.String toString();
public java.lang.String toString_d();
static int parseFlags(java.lang.String) throws jregex.PatternSyntaxException;
static int parseFlags(char[], int, int) throws jregex.PatternSyntaxException;
private static int getFlag(char) throws jregex.PatternSyntaxException;
}
其中,要关心private和protected成员,因为在使用类的时候,我们只要关心public成员就行了,但 是,要阅读源代码,明白类的构成,就必须注意private和protected成员。
private Pattern() throws PatternSyntaxException{}
public Pattern(String regex) throws PatternSyntaxException{
this(regex,DEFAULT);
}
public Pattern(String regex,String flags) throws PatternSyntaxException{
stringRepr=regex;
compile(regex,parseFlags(flags));
}
public Pattern(String regex, int flags) throws PatternSyntaxException{
stringRepr=regex;
compile(regex,flags);
}
可以看出,构造函数中,有一个缺省的构造函数是private。而第二个调用了最后一个构造函数,用 this()。第三个和最后一个都是用了一个函数compile来完成构造正则表达式的任务。在上面javap的 输出我们也可以看到,compile是一个private函数。
来看看它的说明:
private void compile(String regex,int flags) throws PatternSyntaxException{
Term.makeTree(regex,flags,this);
}
具体到Term类,超出了本文的范围,它采用了hashtable等方法,构造了一个正则表达式,并返回。 而我们关心的是库的结构。从这里我们可以明白一点:构造函数往往需要调用另外一个构造函数来 完成,而不需要把同样的代码在各个构造函数中都实现。同时,也可以采用另外一个private函数来 完成构造函数的功能,而只要在构造函数中调用它就行了。
从Matcher API说明文档中,我们可以看到,有两种通过Pattern实例构造Matcher实例的方法,而在 javap的输出中可以看到,有几种不同的matcher函数。
public Matcher matcher(){
return new Matcher(this);
}
public Matcher matcher(String s){
Matcher m=new Matcher(this);
m.setTarget(s);
return m;
}
public Matcher matcher(char[] data,int start,int end){
Matcher m=new Matcher(this);
m.setTarget(data,start,end);
return m;
}
public Matcher matcher(MatchResult res,int groupId){
Matcher m=new Matcher(this);
if(res instanceof Matcher){
m.setTarget((Matcher)res,groupId);
}
else{
m.setTarget(res.targetChars(),res.start(groupId)+res.targetStart(),res.length(groupId));
}
return m;
}
public Matcher matcher(Reader text,int length)throws IOException{
Matcher m=new Matcher(this);
m.setTarget(text,length);
return m;
}
以上都是用来实现第一种返回matcher的方法。可以看到,这几种实现都是通过new Matcher实例, 以this(即Pattern实例)为参数,然后根据参数不同调用Matcher.setTarget()方法。
public Matcher matcher(MatchResult res,String groupName){
Integer id=res.pattern().groupId(groupName);
if(id==null) throw new IllegalArgumentException("group not found:"+groupName);
int group=id.intValue();
return matcher(res,group);
}
这是第二种返回matcher的方法。
从这里我们可以发现一种比较好的方法:当你需要两个互相关联的类,一个类的实例需要构造另一 个类的实例,可以在第一个类中用一个public方法,方法实现为调用第二个类的构造函数,并可以 用构造出来的实例调用其他方法,根据第一个类的实例的数据来设置第二个类的实例。
同样的,也有Replacer, 和Tokenizer的构造方法。
public Replacer replacer(String expr){
return new Replacer(this,expr);
}
/**
* Returns a replacer will substitute all occurences of a pattern
* through applying a user-defined substitution model.
* @param model a Substitution object which is in charge for match substitution
* @see Replacer
*/
public Replacer replacer(Substitution model){
return new Replacer(this,model);
}
public RETokenizer tokenizer(String text){
return new RETokenizer(this,text);
}
/**
* Tokenizes a specified region by an occurences of the pattern.
* Note that a series of adjacent matches are regarded as a single separator.
* The same as new RETokenizer(Pattern,char[],int,int);
* @see RETokenizer
* @see RETokenizer#RETokenizer(jregex.Pattern,char[],int,int)
*/
public RETokenizer tokenizer(char[] data,int off,int len){
return new RETokenizer(this,data,off,len);
}
/**
* Tokenizes a specified region by an occurences of the pattern.
* Note that a series of adjacent matches are regarded as a single separator.
* The same as new RETokenizer(Pattern,Reader,int);
* @see RETokenizer
* @see RETokenizer#RETokenizer(jregex.Pattern,java.io.Reader,int)
*/
public RETokenizer tokenizer(Reader in,int length) throws IOException{
return new RETokenizer(this,in,length);
}
回忆一下,我在本文的开头,曾经提到过:"一个好的库必须是一个紧凑的关系紧密的整体,而不 是一个分散的关系松散的对象的集合。"从API说明文档所显示的这个库的树形结构,并不能看出这 些类之间的联系。而从源代码的角度,我们则可以清楚地看到这一点。在这一部分的讨论中,我们 也明白了两点:
1、如何编写重载构造函数
2、在一个类的实例中返回另外一个类的实例
接下来,看看Matcher类。这个类实现了MatchResult interface. 看看MatchResult的定义:
[games]$javap -classpath .. -s jregex.MatchResult
Compiled from jregex/MatchResult.java
public interface jregex.MatchResult
/* ACC_SUPER bit NOT set */
{
public static final int MATCH;
/* I */
public static final int PREFIX;
/* I */
public static final int SUFFIX;
/* I */
public static final int TARGET;
/* I */
public abstract jregex.Pattern pattern();
/* ()Ljregex/Pattern; */
public abstract int groupCount();
/* ()I */
public abstract boolean isCaptured();
/* ()Z */
public abstract boolean isCaptured(int);
/* (I)Z */
public abstract boolean isCaptured(java.lang.String);
/* (Ljava/lang/String;)Z */
public abstract java.lang.String group(int);
/* (I)Ljava/lang/String; */
public abstract boolean getGroup(int, java.lang.StringBuffer);
/* (ILjava/lang/StringBuffer;)Z */
public abstract boolean getGroup(int, jregex.TextBuffer);
/* (ILjregex/TextBuffer;)Z */
public abstract java.lang.String group(java.lang.String);
/* (Ljava/lang/String;)Ljava/lang/String; */
public abstract boolean getGroup(java.lang.String, java.lang.StringBuffer);
/* (Ljava/lang/String;Ljava/lang/StringBuffer;)Z */
public abstract boolean getGroup(java.lang.String, jregex.TextBuffer);
/* (Ljava/lang/String;Ljregex/TextBuffer;)Z */
public abstract java.lang.String prefix();
/* ()Ljava/lang/String; */
public abstract java.lang.String suffix();
/* ()Ljava/lang/String; */
public abstract java.lang.String target();
/* ()Ljava/lang/String; */
public abstract int targetStart();
/* ()I */
public abstract int targetEnd();
/* ()I */
public abstract char targetChars()[];
/* ()[C */
public abstract int start();
/* ()I */
public abstract int end();
/* ()I */
public abstract int length();
/* ()I */
public abstract int start(int);
/* (I)I */
public abstract int end(int);
/* (I)I */
public abstract int length(int);
/* (I)I */
public abstract char charAt(int);
/* (I)C */
public abstract char charAt(int, int);
/* (II)C */
}
jregex.MatchResult定义了一些abstract函数。有什么作用?在后面我们将会讨论到。
再看看Matcher的实现。
[games]$javap -classpath .. -s jregex.Matcher
Compiled from jregex/Matcher.java
public class jregex.Matcher extends java.lang.Object implements jregex.MatchResult {
public static final int ANCHOR_START;
/* I */
public static final int ANCHOR_LASTMATCH;
/* I */
public static final int ANCHOR_END;
/* I */
public static final int ACCEPT_INCOMPLETE;
/* I */
jregex.Matcher(jregex.Pattern);
/* (Ljregex/Pattern;)V */
public final void setTarget(jregex.Matcher, int);
/* (Ljregex/Matcher;I)V */
public void setTarget(java.lang.String);
/* (Ljava/lang/String;)V */
public void setTarget(java.lang.String, int, int);
/* (Ljava/lang/String;II)V */
public void setTarget(char[], int, int);
/* ([CII)V */
public final void setTarget(char[], int, int, boolean);
/* ([CIIZ)V */
public void setTarget(java.io.Reader, int) throws java.io.IOException;
/* (Ljava/io/Reader;I)V */
public final boolean isStart();
/* ()Z */
public final boolean matches();
/* ()Z */
public final boolean matches(java.lang.String);
/* (Ljava/lang/String;)Z */
public void setPosition(int);
/* (I)V */
public final boolean find();
/* ()Z */
public final boolean find(int);
/* (I)Z */
public jregex.MatchIterator findAll();
/* ()Ljregex/MatchIterator; */
public jregex.MatchIterator findAll(int);
/* (I)Ljregex/MatchIterator; */
public final boolean proceed();
/* ()Z */
public final boolean proceed(int);
/* (I)Z */
public final void skip();
/* ()V */
public java.lang.String toString();
/* ()Ljava/lang/String; */
public jregex.Pattern pattern();
/* ()Ljregex/Pattern; */
public java.lang.String target();
/* ()Ljava/lang/String; */
public char targetChars()[];
/* ()[C */
public int targetStart();
/* ()I */
public int targetEnd();
/* ()I */
public char charAt(int);
/* (I)C */
public char charAt(int, int);
/* (II)C */
public final int length();
/* ()I */
public final int start();
/* ()I */
public final int end();
/* ()I */
public java.lang.String prefix();
/* ()Ljava/lang/String; */
public java.lang.String suffix();
/* ()Ljava/lang/String; */
public int groupCount();
/* ()I */
public java.lang.String group(int);
/* (I)Ljava/lang/String; */
public java.lang.String group(java.lang.String);
/* (Ljava/lang/String;)Ljava/lang/String; */
public boolean getGroup(int, jregex.TextBuffer);
/* (ILjregex/TextBuffer;)Z */
public boolean getGroup(java.lang.String, jregex.TextBuffer);
/* (Ljava/lang/String;Ljregex/TextBuffer;)Z */
public boolean getGroup(int, java.lang.StringBuffer);
/* (ILjava/lang/StringBuffer;)Z */
public boolean getGroup(java.lang.String, java.lang.StringBuffer);
/* (Ljava/lang/String;Ljava/lang/StringBuffer;)Z */
public java.lang.String groups()[];
/* ()[Ljava/lang/String; */
public java.util.Vector groupv();
/* ()Ljava/util/Vector; */
public final boolean isCaptured();
/* ()Z */
public final boolean isCaptured(int);
/* (I)Z */
public final boolean isCaptured(java.lang.String);
/* (Ljava/lang/String;)Z */
public final int length(int);
/* (I)I */
public final int start(int);
/* (I)I */
public final int end(int);
/* (I)I */
public java.lang.String toString_d();
/* ()Ljava/lang/String; */
static {};
/* ()V */
先来看看它的构造函数,这个函数在Pattern中被调用用来构造Matcher类的实例。
Matcher(Pattern regex){
//注意下面这一行,它说明Matcher类的内部有一个指向Pattern实例的reference.
this.re=regex;
//int memregCount=(memregs=new MemReg[regex.memregs]).length;
//for(int i=0;i
// this.memregs[i]=new MemReg(-1); //unlikely to SearchEntry, in this case we know memreg indicies by d
efinition
//}
//counters=new int[regex.counters];
//int lookaheadCount=(lookaheads=new LAEntry[regex.lookaheads]).length;
//for(int i=0;i
// this.lookaheads[i]=new LAEntry();
//}
//定义了一些内部的数据,MemReg是一个有三个整数的类。类的声明见下。
int memregCount,counterCount,lookaheadCount;
if((memregCount=regex.memregs)>0){
MemReg[] memregs=new MemReg[memregCount];
for(int i=0;i
memregs[i]=new MemReg(-1); //unlikely to SearchEntry, in this case we know memreg indicies by defin
ition
}
this.memregs=memregs;
}
if((counterCount=regex.counters)>0) counters=new int[counterCount];
//定义了一些内部的数据。类的声明见下。
if((lookaheadCount=regex.lookaheads)>0){
LAEntry[] lookaheads=new LAEntry[lookaheadCount];
for(int i=0;i
lookaheads[i]=new LAEntry();
}
this.lookaheads=lookaheads;
}
this.memregCount=memregCount;
this.counterCount=counterCount;
this.lookaheadCount=lookaheadCount;
first=new SearchEntry(memregCount,counterCount);
defaultEntry=new SearchEntry(memregCount,counterCount);
minQueueLength=regex.stringRepr.length()/2; // evaluation!!!
}
把这两个类说明为default属性,说明这两个类在jregex这个包的内部可见,而在外部是不可见的。这 两个类的作用是专有的,而不是通用的。回忆一下本文前面提到过的,包的封装,"如果你不想让 类被外部的代码使用,可以用缺省的属性,去掉public."
class MemReg{
int index;
int in=-1,out=-1;
int tmp=-1; //for assuming at GROUP_IN
MemReg(int index){
this.index=index;
}
void reset(){
in=out=-1;
}
}
class LAEntry{
int index;
SearchEntry top,actual;
}
另外,关于包的结构和包中的类的关系,我们感兴趣的还有,MatchIterator。
[games]$javap -classpath .. -s jregex.MatchIterator
Compiled from jregex/MatchIterator.java
public interface jregex.MatchIterator
/* ACC_SUPER bit NOT set */
{
public abstract boolean hasMore();
/* ()Z */
public abstract jregex.MatchResult nextMatch();
/* ()Ljregex/MatchResult; */
public abstract int count();
/* ()I */
}
这是一个interface, 定义了iterator的常用方法,列出所有的MatchResult的实例。
从这里可以看出,定义一个MatchResult interface然后再用Matcher来实现这个interface是一种比较好 的方法。因为,这样定义了比较好的层次结构,对于后面的扩展,程序的更新发展都有比较大的好 处。在这里也可以看出,MatchIterator的方法返回的也是MatchResult interface, 而不是Matcher类。 同时可以看到MatchIterator本身也是一个interface。这就体现了另外一个我们需要明白的重要问题: 对interface编程,而不是对类编程。具体说来,如果在以后重新写了一个新的类,叫做 AnotherMatcher, 同样也是实现MatchResult interface, 在AnotherMatcher中同样可以使用方法来返回一 个MatchIterator的实例,它也许是Matcher类的实例,也可以是AnotherMatcher类的实例。但是,虽 然我们作了很多改动,但是没有影响到以前的程序,不需要对以前的程序作任何的改动。从使用者 的角度来看,如果他只是使用了MatchResult和MatchIterator这两个interfaces中定义的方法,那么, 不论是用Matcher还是AnotherMatcher,他都不需要修改他的代码。
下一个需要看的函数是findAll().
//调用了带参数的重载函数findAll(int)
public MatchIterator findAll(){
return findAll(0);
}
/**
* Returns an iterator over the matches found by subsequently calling find(options), the search starts from t
he zero position.
*/
public MatchIterator findAll(final int options){
//setPosition(0);
//不用关心具体的实现方法,但是我们可以看到,在这里它定义了一个anonymous inner class, 这 个inner class实现了MatchIterator,用于把Matcher中匹配到的结果一个个的返回。这个inner class的 对象实例的reference被findAll()方法返回。能够用于外部的一个reference,并能读出这些结果。这也 是一种需要注意的方法。
return new MatchIterator(){
private boolean checked=false;
private boolean hasMore=false;
public boolean hasMore(){
if(!checked) check();
return hasMore;
}
public MatchResult nextMatch(){
if(!checked) check();
if(!hasMore) throw new NoSuchElementException();
checked=false;
return Matcher.this;
}
private final void check(){
hasMore=find(options);
checked=true;
}
public int count(){
if(!checked) check();
if(!hasMore) return 0;
int c=1;
while(find(options))c++;
checked=false;
return c;
}
};
}
在上面这段代码中,我们明白了如下3点:
1、对interface编程,而不是对类编程。
2、如何编写一个iterator(迭代器)。需要有起码的两个函数:hasMore(), NextElement()。这样,使用 者可以用如下循环读取迭代器中的内容:
for(;iterator.hasMore();)
iterator.NextElement();
3、把处理方法和处理结果分开。用Matcher来表示匹配的方法的具体实现,而把结果放在另外一个 类中。这样做的好处是处理方法和结果分开,更加的灵活可以任意的更改处理的方法,而结果类不 受到影响。也可以更改结果类的读取方式,但是不影响处理方法类的实现。这种方法已经被J2SE SDK中的很多集合类所使用。也可以参考它们的实现方法。
当然,还有一点需要注意的就是使用了匿名的inner class方法。
jregex中可以讨论的问题还有很多,比如它所采用的Not Finite Automata方法,它所采用的正则表达 式规范等等。但是,作为本文所讨论的范围,即如何设计和实现一个库,已经可以展示它的设计方 法了。有兴趣的读者可以到jregex.sourceforge.net下载它的源代码包,二进制文件包,使用范例和文 档说明做进一步的研究。
小结:
在本文中,我们讨论了设计和实现一个Java库的若干原则,并用一个简单的例子来说明了这些原 则。随后,我们用一个实际应用范围很广的库来讨论了其他的一些需要注意的问题。
设计原则一:封装
一个好的库必须是一个紧凑的关系紧密的整体,而不是一个分散的关系松散的对象的集合。
设计原则二:继承
采用abstract函数,interface, 和"钩子"函数。
设计原则三:调试
在库代码中加入打印调试信息的语句。
以及一些设计方法:
1、如何编写重载构造函数。
2、在一个类的实例中返回另外一个类的实例
3、对interface编程,而不是对类编程。
4、如何编写一个iterator(迭代器)。
5、把处理方法和处理结果分开。
6、如何使用inner class.
同时,我们也讨论了阅读一个Java库源代码的方法:
1、阅读API文档说明,库的树形结构,明白类,interface的关系。
2、用javap来列出类的函数说明,变量,interface中定义的函数,常量等等。
3、注意private, protected函数。