Java程序员:一刻钟精通正则表达式
想必很多人都对正则表达式都头疼。今天,我以我的认识,加上网上一些文章,希望用常人都可以理解的表达方式来和大家分享学习经验。
开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明:
"^The": 开头一定要有"The"字符串;
"of despair$": 结尾一定要有"of despair" 的字符串;
那么,
"^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。
"notice": 匹配包含notice的字符串。
你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。
接着,说说 '*', '+',和 '?',
他们用来表示一个字符可以出现的次数或者顺序。 他们分别表示:
"zero or more"相当于{0,},
"one or more"相当于{1,},
"zero or one."相当于{0,1}, 这里是一些例子:
"ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等);
"ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等。);
"ab?":和ab{0,1}同义,可以没有或者只有一个b;
"a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串。
要点, '*', '+',和 '?'只管它前面那个字符。
你也可以在大括号里面限制字符出现的个数,比如
"ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb");
"ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等。);
"ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb")。
现在我们把一定几个字符放到小括号里,比如:
"a(bc)*": 匹配 a 后面跟0个或者一个"bc";
"a(bc){1,5}": 一个到5个 "bc."
还有一个字符 '│', 相当于OR 操作:
"hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串;
"(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串;
"(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;
一个点('.')可以代表所有的单一字符,不包括"\n"
如果,要匹配包括"\n"在内的所有单个字符,怎么办?
对了,用'[\n.]'这种模式。
"a.[0-9]": 一个a加一个字符再加一个0到9的数字
"^.{3}$": 三个任意字符结尾 .
中括号括住的内容只匹配一个单一的字符
"[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样);
"[a-d]": 匹配'a' 到'd'的单个字符 (和"a│b│c│d" 还有 "[abcd]"效果一样); 一般我们都用[a-zA-Z]来指定字符为一个大小写英文
"^[a-zA-Z]": 匹配以大小写字母开头的字符串
"[0-9]%": 匹配含有 形如 x% 的字符串
",[a-zA-Z0-9]$": 匹配以逗号再加一个数字或字母结尾的字符串
你也可以把你不想要得字符列在中括号里,你只需要在总括号里面使用'^' 作为开头 "%[^a-zA-Z]%" 匹配含有两个百分号里面有一个非字母的字符串。
要点:^用在中括号开头的时候,就表示排除括号里的字符。为了PHP能够解释,你必须在这些字符面前后加'',并且将一些字符转义。
不要忘记在中括号里面的字符是这条规路的例外?在中括号里面, 所有的特殊字符,包括(''), 都将失去他们的特殊性质 "[*\+?{}.]"匹配含有这些字符的字符串。
还有,正如regx的手册告诉我们: "如果列表里含有 ']', 最好把它作为列表里的第一个字符(可能跟在'^'后面)。 如果含有'-', 最好把它放在最前面或者最后面, or 或者一个范围的第二个结束点[a-d-0-9]中间的‘-’将有效。
看了上面的例子,你对{n,m}应该理解了吧。要注意的是,n和m都不能为负整数,而且n总是小于m. 这样,才能 最少匹配n次且最多匹配m次。 如"p{1,5}"将匹配 "pvpppppp"中的前五个p.
下面说说以\开头的
\b 书上说他是用来匹配一个单词边界,就是……比如've\b',可以匹配love里的ve而不匹配very里有ve
\B 正好和上面的\b相反。例子我就不举了
……突然想起来……可以到http://www.phpv.net/article.php/251 看看其它用\ 开头的语法
好,我们来做个应用:
如何构建一个模式来匹配 货币数量 的输入
构建一个匹配模式去检查输入的信息是否为一个表示money的数字。我们认为一个表示money的数量有四种方式: "10000.00" 和 "10,000.00",或者没有小数部分, "10000" and "10,000". 现在让我们开始构建这个匹配模式:
^[1-9][0-9]*$
这是所变量必须以非0的数字开头。但这也意味着 单一的 "0" 也不能通过测试。 以下是解决的方法:
^(0│[1-9][0-9]*)$
"只有0和不以0开头的数字与之匹配",我们也可以允许一个负号在数字之前:
^(0│-?[1-9][0-9]*)$
这就是: "0 或者 一个以0开头 且可能 有一个负号在前面的数字。" 好了,现在让我们别那么严谨,允许以0开头。现在让我们放弃 负号 , 因为我们在表示钱币的时候并不需要用到。 我们现在指定 模式 用来匹配小数部分:
^[0-9]+(\.[0-9]+)?$
这暗示匹配的字符串必须最少以一个阿拉伯数字开头。 但是注意,在上面模式中 "10." 是不匹配的, 只有 "10" 和 "10.2" 才可以。 (你知道为什么吗)
^[0-9]+(\.[0-9]{2})?$
我们上面指定小数点后面必须有两位小数。如果你认为这样太苛刻,你可以改成:
^[0-9]+(\.[0-9]{1,2})?$
这将允许小数点后面有一到两个字符。 现在我们加上用来增加可读性的逗号(每隔三位), 我们可以这样表示:
^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]{1,2})?$
不要忘记 '+' 可以被 '*' 替代 如果你想允许空白字符串被输入话 (为什么?)。 也不要忘记反斜杆 ‘\’ 在php字符串中可能会出现错误 (很普遍的错误)。
现在,我们已经可以确认字符串了, 我们现在把所有逗号都去掉 str_replace(",", "", $money) 然后在把类型看成 double然后我们就可以通过他做数学计算了。
再来一个:
构造检查email的正则表达式
在一个完整的email地址中有三个部分:
1. 用户名 (在 '@' 左边的一切),
2.'@',
3. 服务器名(就是剩下那部分)。
用户名可以含有大小写字母阿拉伯数字,句号 ('.'), 减号('-'), and 下划线 ('_')。 服务器名字也是符合这个规则,当然下划线除外。
现在, 用户名的开始和结束都不能是句点。 服务器也是这样。 还有你不能有两个连续的句点他们之间至少存在一个字符,好现在我们来看一下怎么为用户名写一个匹配模式:
^[_a-zA-Z0-9-]+$
现在还不能允许句号的存在。 我们把它加上:
^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$
上面的意思就是说: "以至少一个规范字符(除了。)开头,后面跟着0个或者多个以点开始的字符串。"
简单化一点, 我们可以用 eregi()取代 ereg()。eregi()对大小写不敏感, 我们就不需要指定两个范围 "a-z" 和 "A-Z" ? 只需要指定一个就可以了:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*$
后面的服务器名字也是一样,但要去掉下划线:
^[a-z0-9-]+(\.[a-z0-9-]+)*$
好。 现在只需要用“@”把两部分连接:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$
这就是完整的email认证匹配模式了,只需要调用
eregi(‘^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ ’,$eamil)
就可以得到是否为email了。
正则表达式的其他用法
提取字符串
ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分(具体用法你可以阅读手册)。 比如说,我们想从 path/URL 提取文件名 ? 下面的代码就是你需要:
ereg("([^\\/]*)$", $pathOrUrl, $regs);
echo $regs[1];
高级的代换
ereg_replace() 和 eregi_replace()也是非常有用的: 假如我们想把所有的间隔负号都替换成逗号:
ereg_replace("[ \n\r\t]+", ",", trim($str));
最后,我把另一串检查EMAIL的正则表达式让看文章的你来分析一下。
"^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.'@'.'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$"
如果能方便的读懂,那这篇文章的目的就达到了。
原文地址:http://java.chinaitlab.com/base/732793.html
posted @
2008-02-29 09:49 CoderDream 阅读(955) |
评论 (0) |
编辑 收藏
1、
揭开正则表达式的神秘面纱
2、
正则表达式话题
3、
如何使用Java自带的正则表达式
4、
处理正则表达式的java包:regexp
5、
Java正则表达式技巧总结
6、
学点Java正则表达式
7、
在JAVA中使用正则表达式
8、
Java正则表达式(1)
9、
java正则表达式; regular expression(2)
10、
JAVA正则表达式(2)
posted @
2008-02-29 09:47 CoderDream 阅读(319) |
评论 (0) |
编辑 收藏
JAVA 正则表达式实现
§1黑暗岁月
有一个String,如何查询其中是否有y和f字符?最黑暗的办法就是:
程序1:我知道if、for语句和charAt()啊。
class Test{
public static void main(String args[]) {
String str="For my money, the important thing "+
"about the meeting was bridge-building";
char x='y';
char y='f';
boolean result=false;
for(int i=0;i<str.length();i++){
char z=str.charAt(i); //System.out.println(z);
if(x==z||y==z) {
result=true;
break;
}
else result=false;
}
System.out.println(result);
}
}
好像很直观,但这种方式难以应付复杂的工作。如查询一段文字中,是否有is?是否有thing或ting等。这是一个讨厌的工作。
§2 Java的java.util.regex包
按照面向对象的思路,把希望查询的字符串如is、thing或ting封装成一个对象,以这个对象作为模板去匹配一段文字,就更加自然了。作为模板的那个东西就是下面要讨论的正则表达式。先不考虑那么复杂,看一个例子:
程序2:不懂。先看看可以吧?
import java.util.regex.*;
class Regex1{
public static void main(String args[]) {
String str="For my money, the important thing "+
"about the meeting was bridge-building";
String regEx="a|f"; //表示a或f
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher(str);
boolean result=m.find();
System.out.println(result);
}
}
如果str匹配regEx,那么result为true,否则为flase。如果想在查找时忽略大小写,则可以写成:
Pattern p=Pattern.compile(regEx,Pattern.CASE_INSENSITIVE);
虽然暂时不知道Pattern(模板、模式)和Matcher(匹配器)的细节,程序的感觉就比较爽,如果先查询is、后来又要查询thing或ting,我们只需要修改一下模板Pattern,而不是考虑if语句和for语句,或者通过charAt()。
1、写一个特殊的字符串??正则表达式如a|f。
2、将正则表达式编译成一个模板:p
3、用模板p去匹配字符串str。
思路清楚了,现在看Java是如何处理的(Java程序员直到JDK1.4才能使用这些类。
§3 Pattern类与查找
①public final class java.util.regex.Pattern是正则表达式编译后的表达法。下面的语句将创建一个Pattern对象并赋值给句柄p:Pattern p=Pattern.compile(regEx);
有趣的是,Pattern类是final类,而且它的构造器是private。也许有人告诉你一些设计模式的东西,或者你自己查有关资料。这里的结论是:Pattern类不能被继承,我们不能通过new创建Pattern类的对象。
因此在Pattern类中,提供了2个重载的静态方法,其返回值是Pattern对象(的引用)。如:
public static Pattern compile(String regex) {
return new Pattern(regex, 0);
}
当然,我们可以声明Pattern类的句柄,如Pattern p=null;
②p.matcher(str)表示以用模板p去生成一个字符串str的匹配器,它的返回值是一个Matcher类的引用,为什么要这个东西呢?按照自然的想法,返回一个boolean值不行吗?
我们可以简单的使用如下方法:
boolean result=Pattern.compile(regEx).matcher(str).find();
呵呵,其实是三个语句合并的无句柄方式。无句柄常常不是好方式。后面再学习Matcher类吧。先看看regEx??这个怪咚咚。
§4 正则表达式之限定符
正则表达式(Regular Expression)是一种生成字符串的字符串。晕吧。比如说,String regEx="me+";这里字符串me+能够生成的字符串是:me、mee、meee、meeeeeeeeee等等,一个正则表达式可能生成无穷的字符串,所以我们不可能(有必要吗?)输出正则表达式产生的所有东西。
反过来考虑,对于字符串:me、mee、meee、meeeeeeeeee等等,我们能否有一种语言去描述它们呢?显然,正则表达式语言是这种语言,它是一些字符串的模式??简洁而深刻的描述。
我们使用正则表达式,用于字符串查找、匹配、指定字符串替换、字符串分割等等目的。
生成字符串的字符串??正则表达式,真有些复杂,因为我们希望由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)描述任意的字符串,而且要准确。
先搞几个正则表达式例子:
程序3:我们总用这个程序测试正则表达式。
import java.util.regex.*;
class Regex1{
public static void main(String args[]) {
String str="For my money, the important thing ";
String regEx="ab*";
boolean result=Pattern.compile(regEx).matcher(str).find();
System.out.println(result);
}
}//ture
①"ab*"??能匹配a、ab、abb、abbb……。所以,*表示前面字符可以有零次或多次。如果仅仅考虑查找,直接用"a"也一样。但想想替换的情况。 问题regEx="abb*"结果如何?
②"ab+"??能匹配ab、abb、abbb……。等价于"abb*"。问题regEx="or+"结果如何?
③"or?"??能匹配o和or。? 表示前面字符可以有零次或一次。
这些限定符*、+、?方便地表示了其前面字符(子串)出现的次数(我们用{}来描述):
x* 零次或多次 ≡{0,}
x+ 一次或多次 ≡{1,}
x? 零次或一次 ≡{0,1}
x{n} n次(n>0)
x{n,m} 最少n次至最多m次(0<n<m)
x{n,} 最少n次,
现在我们知道了连续字符串的查找、匹配。下面的是一些练习题:
①查找粗体字符串(不要求精确或要求精确匹配),写出其正则表达式:
str regEX(不要求精确) regEX(要求精确) 试一试
abcffd b或bcff或bcf*或bc*或bc+ bcff或bcf{2} bc{3}
gooooogle o{1,}、o+ o{5}
banana (an)+ (an){2}a、a(na) {2}
②正则表达式匹配字符串,输出是什么?
§5替换(删除)、Matcher类
现在我们可能厌烦了true/false,我们看看替换。如把book,google替换成bak(这个文件后缀名,在EditPlus中还行)、look或goooogle。
程序4:字符串的替换。
import java.util.regex.*;
class Regex1{
public static void main(String args[]) {
String regEx="a+";//表示一个或多个a
String str="abbbaaa an banana hhaana";
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher(str);
String s=m.replaceAll("⊙⊙"); // ("") 删除
System.out.println(s);
}
}
这个程序与前面的程序的区别,在于使用了m.replaceAll(String)方法。看来Matcher类还有点用处。
① Matcher是一个匹配器。可以把他看成一个人,一手拿着模子(Pattern类的对象),一手拿着一个字符序列(CharSequence),通过解释该模子而对字符序列进行匹配操作(match operations)。常常我们这样编程:“喂,模子p,你和字符串str一起创建一个匹配器对象”。即Matcher m=p.matcher(str);
② m可以进行一些操作,如public String replaceAll(String replacement),它以replacement替换所有匹配的字符串。
§6正则表达式之特殊字符
我们熟悉这样一个字符串"\n" 如:System.out.print(s+"\nbbb");这是Java中常用的转移字符之一。其实转移字符就是一种正则表达式,它使用了特殊字符 \ 。
下面是正则表达式中常用的特殊字符:
匹配次数符号 * + ? {n}、{n,}、{n,m}
“或”符号 | 程序2已经使用过了
句点符号 . 句点符号匹配所有字符(一个),包括空格、Tab字符甚至换行符。
方括号 [ ] 仅仅匹配方括号其中的字符)
圆括号 () 分组,圆括号中的字符视为一个整体。
连字符 - 表示一个范围。
“否”符号 ^ 表示不希望被匹配的字符(排除)
我们一下子学不了太多的东西,这不是正则表达式的全部内容和用法。但已经够我们忙活的了。我们用程序4 验证。(⊙⊙表示替换的字符)
① regEx为下列字符串时,能够表示什么?
regEx 匹配 测试用str
(a|b){2} aa、ab、bb、ba aabbfooaabfooabfoob
a[abc]b aab、abb、acb 3dfacb5ooyfo6abbfooaab
. all string 3dfac
a. aa、ax……等等 3dfacgg
d[^j]a daa、d9a等等,除dja 3dfacggdjad5a
[d-g][ac]c dac、ecc、gac等 3dfacggggccad5c
[d-g].{2}c d⊙⊙c…… 3dfacggggccad5c
g{1,10} g、ggg…… 3dfacggggccad5c
[a|c][^a] 3dfacggggccad5c
② 下列字符串如何用regEx表示?
测试用str 匹配 regEx
aabbfoaoabfooafobob a⊙⊙b a..b
aabbfoaaobfooafbob a⊙b、除aab a[^a]b、
gooooooogle oooo……变成oo o{2,20}
一本书中的“tan”、“ten”、“tin”和“ton” t.n、t[aeio]n
abcaccbcbaacabccaa 删除ac、ca (ca)|(ac)
abccbcbaabca 再删除ab、ba 结果ccbcca(如何与上面的合并)
注:
1、String str="一本书中的tan、ten、tin和ton";
输出: 一本书中的⊙⊙、⊙⊙、⊙⊙和⊙⊙
2、String str=" abcaccbcbaacabccaa "; 输出:ccbcca
程序5:if、for语句和charAt(),886。
import java.util.regex.*;
class Regex1{
public static void main(String args[]) {
String str="abcaccbcbaacabccaa";
String regEx="(ac)|(ca)";
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher(str);
String s=m.replaceAll("");//⊙⊙
regEx="(ab)|(ba)";
p=Pattern.compile(regEx);
s=p.matcher(s).replaceAll("");
System.out.print(s+"\n");
}
}
§7 开始
好像我们知道了一些正则表达式与 Java的知识,事实上,我们才刚刚开始。这里列出我们知道的东西,也说一点我们不知道的东西。
① Java在JDK1.4引入了(java.util.regex包)以支持正则表达式,包中有两个类,分别是Pattern和Matcher。它们都有很多的方法,我们还不知道。String类中的split、matches方法等等也使用到了正则表达式。StringTokenizer是否没有用处了?
② 正则表达式是一门语言。有许多正则表达式语法、选项和特殊字符,在Pattern.java源文件中大家可以查看。可能比想象中的要复杂。系统学习正则表达式的历史、语法、全部特殊字符(相当于Java中的关键字的地位),组合逻辑是下一步的事情。
③ 正则表达式是文本处理的重要技术,在Perl、PHP、Python、JavaScript、Java、C#中被广泛支持。被列为“保证你现在和未来不失业的十种关键技术”,呵呵,信不信由你
posted @
2008-02-28 14:35 CoderDream 阅读(441) |
评论 (0) |
编辑 收藏
JAVA 正则表达式4种常用的功能
正则表达式在字符串处理上有着强大的功能,sun在jdk1.4加入了对它的支持
下面简单的说下它的4种常用功能:
查询:
以下是代码片段:
String str="abc efg ABC";
String regEx="a|f"; //表示a或f
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher(str);
boolean rs=m.find();
如果str中有regEx,那么rs为true,否则为flase。如果想在查找时忽略大小写,则可以写成Pattern p=Pattern.compile(regEx,Pattern.CASE_INSENSITIVE);
提取:
以下是代码片段:
String regEx=".+\(.+)$";
String str="c:\dir1\dir2\name.txt";
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher(str);
boolean rs=m.find();
for(int i=1;i<=m.groupCount();i++){
System.out.println(m.group(i));
}
以上的执行结果为name.txt,提取的字符串储存在m.group(i)中,其中i最大值为m.groupCount();
分割:
以下是代码片段:
String regEx="::";
Pattern p=Pattern.compile(regEx);
String[] r=p.split("xd::abc::cde");
执行后,r就是{"xd","abc","cde"},其实分割时还有跟简单的方法:
String str="xd::abc::cde";
String[] r=str.split("::");
替换(删除):
以下是代码片段:
String regEx="a+"; //表示一个或多个a
Pattern p=Pattern.compile(regEx);
Matcher m=p.matcher("aaabbced a ccdeaa");
String s=m.replaceAll("A");
结果为"Abbced A ccdeA"
如果写成空串,既可达到删除的功能,比如:
String s=m.replaceAll("");
结果为"bbced ccde"
附:
\D 等於 [^0-9] 非数字
\s 等於 [ \t\n\x0B\f ] 空白字元
\S 等於 [^ \t\n\x0B\f ] 非空白字元
\w 等於 [a-zA-Z_0-9] 数字或是英文字
\W 等於 [^a-zA-Z_0-9] 非数字与英文字
^ 表示每行的开头
$ 表示每行的结尾
原文地址:
http://java.chinaitlab.com/advance/350770.html
posted @
2008-02-28 13:41 CoderDream 阅读(320) |
评论 (0) |
编辑 收藏
如果你曾经用过Perl或任何其他内建正则表达式支持的语言,你一定知道用正则表达式处理文本和匹配模式是多么简单。如果你不熟悉这个术语,那么“正则表达式”(Regular Expression)就是一个字符构成的串,它定义了一个用来搜索匹配字符串的模式。
许多语言,包括Perl、PHP、Python、JavaScript和JScript,都支持用正则表达式处理文本,一些文本编辑器用正则表达式实现高级“搜索-替换”功能。那么Java又怎样呢?本文写作时,一个包含了用正则表达式进行文本处理的Java规范需求(Specification Request)已经得到认可,你可以期待在JDK的下一版本中看到它。
然而,如果现在就需要使用正则表达式,又该怎么办呢?你可以从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识,然后以Jakarta-ORO API为例介绍如何使用正则表达式。
一、正则表达式基础知识
我们先从简单的开始。假设你要搜索一个包含字符“cat”的字符串,搜索用的正则表达式就是“cat”。如果搜索对大小写不敏感,单词“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是说:
1.1 句点符号
假设你在玩英文拼字游戏,想要找出三个字母的单词,而且这些单词必须以“t”字母开头,以“n”字母结束。另外,假设有一本英文字典,你可以用正则表达式搜索它的全部内容。要构造出这个正则表达式,你可以使用一个通配符——句点符号“.”。这样,完整的表达式就是“t.n”,它匹配“tan”、“ten”、“tin”和“ton”,还匹配“t#n”、“tpn”甚至“t n”,还有其他许多无意义的组合。这是因为句点符号匹配所有字符,包括空格、Tab字符甚至换行符:
1.2 方括号符号
为了解决句点符号匹配范围过于广泛这一问题,你可以在方括号(“[]”)里面指定看来有意义的字符。此时,只有方括号里面指定的字符才参与匹配。也就是说,正则表达式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配,因为在方括号之内你只能匹配单个字符:
1.3 “或”符号
如果除了上面匹配的所有单词之外,你还想要匹配“toon”,那么,你可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“toon”,使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号,因为方括号只允许匹配单个字符;这里必须使用圆括号“()”。圆括号还可以用来分组,具体请参见后面介绍。
1.4 表示匹配次数的符号
表一显示了表示匹配次数的符号,这些符号用来确定紧靠该符号左边的符号出现的次数:
假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中,连字符(“-”)有着特殊的意义,它表示一个范围,比如从0到9。因此,匹配社会安全号码中的连字符号时,它的前面要加上一个转义字符“\”。
图一:匹配所有123-12-1234形式的社会安全号码
假设进行搜索的时候,你希望连字符号可以出现,也可以不出现——即,999-99-9999和999999999都属于正确的格式。这时,你可以在连字符号后面加上“?”数量限定符号,如图二所示:
图二:匹配所有123-12-1234和123121234形式的社会安全号码
下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分“[0-9]{4}”,再加上字母部分“[A-Z]{2}”。图三显示了完整的正则表达式。
图三:匹配典型的美国汽车牌照号码,如8836KV
1.5 “否”符号
“^”符号称为“否”符号。如果用在方括号内,“^”表示不想要匹配的字符。例如,图四的正则表达式匹配所有单词,但以“X”字母开头的单词除外。
图四:匹配所有单词,但“X”开头的除外
1.6 圆括号和空白符号
假设要从格式为“June 26, 1951”的生日日期中提取出月份部分,用来匹配该日期的正则表达式可以如图五所示:
图五:匹配所有Moth DD,YYYY格式的日期
新出现的“\s”符号是空白符号,匹配所有的空白字符,包括Tab字符。如果字符串正确匹配,接下来如何提取出月份部分呢?只需在月份周围加上一个圆括号创建一个组,然后用ORO API(本文后面详细讨论)提取出它的值。修改后的正则表达式如图六所示:
图六:匹配所有Month DD,YYYY格式的日期,定义月份值为第一个组
1.7 其它符号
为简便起见,你可以使用一些为常见正则表达式创建的快捷符号。如表二所示:
表二:常用符号
例如,在前面社会安全号码的例子中,所有出现“[0-9]”的地方我们都可以使用“\d”。修改后的正则表达式如图七所示:
图七:匹配所有123-12-1234格式的社会安全号码
二、Jakarta-ORO库
有许多源代码开放的正则表达式库可供Java程序员使用,而且它们中的许多支持Perl 5兼容的正则表达式语法。我在这里选用的是Jakarta-ORO正则表达式库,它是最全面的正则表达式API之一,而且它与Perl 5正则表达式完全兼容。另外,它也是优化得最好的API之一。
Jakarta-ORO库以前叫做OROMatcher,Daniel Savarese大方地把它赠送给了Jakarta Project。你可以按照本文最后参考资源的说明下载它。
我首先将简要介绍使用Jakarta-ORO库时你必须创建和访问的对象,然后介绍如何使用Jakarta-ORO API。
▲ PatternCompiler对象
首先,创建一个Perl5Compiler类的实例,并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现,允许你把正则表达式编译成用来匹配的Pattern对象。
▲ Pattern对象
要把正则表达式编译成Pattern对象,调用compiler对象的compile()方法,并在调用参数中指定正则表达式。例如,你可以按照下面这种方式编译正则表达式“t[aeio]n”:
默认情况下,编译器创建一个大小写敏感的模式(pattern)。因此,上面代码编译得到的模式只匹配“tin”、“tan”、 “ten”和“ton”,但不匹配“Tin”和“taN”。要创建一个大小写不敏感的模式,你应该在调用编译器的时候指定一个额外的参数:
创建好Pattern对象之后,你就可以通过PatternMatcher类用该Pattern对象进行模式匹配。
▲ PatternMatcher对象
PatternMatcher对象根据Pattern对象和字符串进行匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现,它根据Perl 5正则表达式语法进行模式匹配:
使用PatternMatcher对象,你可以用多个方法进行匹配操作,这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串:
· boolean matches(String input, Pattern pattern):当输入字符串和正则表达式要精确匹配时使用。换句话说,正则表达式必须完整地描述输入字符串。
· boolean matchesPrefix(String input, Pattern pattern):当正则表达式匹配输入字符串起始部分时使用。
· boolean contains(String input, Pattern pattern):当正则表达式要匹配输入字符串的一部分时使用(即,它必须是一个子串)。
另外,在上面三个方法调用中,你还可以用PatternMatcherInput对象作为参数替代String对象;这时,你可以从字符串中最后一次匹配的位置开始继续进行匹配。当字符串可能有多个子串匹配给定的正则表达式时,用PatternMatcherInput对象作为参数就很有用了。用PatternMatcherInput对象作为参数替代String时,上述三个方法的语法如下:
· boolean matches(PatternMatcherInput input, Pattern pattern)
· boolean matchesPrefix(PatternMatcherInput input, Pattern pattern)
· boolean contains(PatternMatcherInput input, Pattern pattern)
三、应用实例
下面我们来看看Jakarta-ORO库的一些应用实例。
3.1 日志文件处理
任务:分析一个Web服务器日志文件,确定每一个用户花在网站上的时间。在典型的BEA WebLogic日志文件中,日志记录的格式如下:
分析这个日志记录,可以发现,要从这个日志文件提取的内容有两项:IP地址和页面访问时间。你可以用分组符号(圆括号)从日志记录提取出IP地址和时间标记。
首先我们来看看IP地址。IP地址有4个字节构成,每一个字节的值在0到255之间,各个字节通过一个句点分隔。因此,IP地址中的每一个字节有至少一个、最多三个数字。图八显示了为IP地址编写的正则表达式:
图八:匹配IP地址
IP地址中的句点字符必须进行转义处理(前面加上“\”),因为IP地址中的句点具有它本来的含义,而不是采用正则表达式语法中的特殊含义。句点在正则表达式中的特殊含义本文前面已经介绍。
日志记录的时间部分由一对方括号包围。你可以按照如下思路提取出方括号里面的所有内容:首先搜索起始方括号字符(“[”),提取出所有不超过结束方括号字符(“]”)的内容,向前寻找直至找到结束方括号字符。图九显示了这部分的正则表达式。
图九:匹配至少一个字符,直至找到“]”
现在,把上述两个正则表达式加上分组符号(圆括号)后合并成单个表达式,这样就可以从日志记录提取出IP地址和时间。注意,为了匹配“- -”(但不提取它),正则表达式中间加入了“\s-\s-\s”。完整的正则表达式如图十所示。
图十:匹配IP地址和时间标记
现在正则表达式已经编写完毕,接下来可以编写使用正则表达式库的Java代码了。
为使用Jakarta-ORO库,首先创建正则表达式字符串和待分析的日志记录字符串:
这里使用的正则表达式与图十的正则表达式差不多完全相同,但有一点例外:在Java中,你必须对每一个向前的斜杠(“\”)进行转义处理。图十不是Java的表示形式,所以我们要在每个“\”前面加上一个“\”以免出现编译错误。遗憾的是,转义处理过程很容易出现错误,所以应该小心谨慎。你可以首先输入未经转义处理的正则表达式,然后从左到右依次把每一个“\”替换成“\\”。如果要复检,你可以试着把它输出到屏幕上。
初始化字符串之后,实例化PatternCompiler对象,用PatternCompiler编译正则表达式创建一个Pattern对象:
现在,创建PatternMatcher对象,调用PatternMatcher接口的contain()方法检查匹配情况:
接下来,利用PatternMatcher接口返回的MatchResult对象,输出匹配的组。由于logEntry字符串包含匹配的内容,你可以看到类如下面的输出:
3.2 HTML处理实例一
下面一个任务是分析HTML页面内FONT标记的所有属性。HTML页面内典型的FONT标记如下所示:
程序将按照如下形式,输出每一个FONT标记的属性:
在这种情况下,我建议你使用两个正则表达式。第一个如图十一所示,它从字体标记提取出“"face="Arial, Serif" size="+2" color="red"”。
图十一:匹配FONT标记的所有属性
第二个正则表达式如图十二所示,它把各个属性分割成名字-值对。
图十二:匹配单个属性,并把它分割成名字-值对
分割结果为:
现在我们来看看完成这个任务的Java代码。首先创建两个正则表达式字符串,用Perl5Compiler把它们编译成Pattern对象。编译正则表达式的时候,指定Perl5Compiler.CASE_INSENSITIVE_MASK选项,使得匹配操作不区分大小写。
接下来,创建一个执行匹配操作的Perl5Matcher对象。
假设有一个String类型的变量html,它代表了HTML文件中的一行内容。如果html字符串包含FONT标记,匹配器将返回true。此时,你可以用匹配器对象返回的MatchResult对象获得第一个组,它包含了FONT的所有属性:
接下来创建一个PatternMatcherInput对象。这个对象允许你从最后一次匹配的位置开始继续进行匹配操作,因此,它很适合于提取FONT标记内属性的名字-值对。创建PatternMatcherInput对象,以参数形式传入待匹配的字符串。然后,用匹配器实例提取出每一个FONT的属性。这通过指定PatternMatcherInput对象(而不是字符串对象)为参数,反复地调用PatternMatcher对象的contains()方法完成。PatternMatcherInput对象之中的每一次迭代将把它内部的指针向前移动,下一次检测将从前一次匹配位置的后面开始。
本例的输出结果如下:
3.3 HTML处理实例二
下面我们来看看另一个处理HTML的例子。这一次,我们假定Web服务器从widgets.acme.com移到了newserver.acme.com。现在你要修改一些页面中的链接:
执行这个搜索的正则表达式如图十三所示:
图十三:匹配修改前的链接
如果能够匹配这个正则表达式,你可以用下面的内容替换图十三的链接:
注意#字符的后面加上了$1。Perl正则表达式语法用$1、$2等表示已经匹配且提取出来的组。图十三的表达式把所有作为一个组匹配和提取出来的内容附加到链接的后面。
现在,返回Java。就象前面我们所做的那样,你必须创建测试字符串,创建把正则表达式编译到Pattern对象所必需的对象,以及创建一个PatternMatcher对象:
接下来,用com.oroinc.text.regex包Util类的substitute()静态方法进行替换,输出结果字符串:
Util.substitute()方法的语法如下:
这个调用的前两个参数是以前创建的PatternMatcher和Pattern对象。第三个参数是一个Substiution对象,它决定了替换操作如何进行。本例使用的是Perl5Substitution对象,它能够进行Perl5风格的替换。第四个参数是想要进行替换操作的字符串,最后一个参数允许指定是否替换模式的所有匹配子串(Util.SUBSTITUTE_ALL),或只替换指定的次数。
【结束语】在这篇文章中,我为你介绍了正则表达式的强大功能。只要正确运用,正则表达式能够在字符串提取和文本修改中起到很大的作用。另外,我还介绍了如何在Java程序中通过Jakarta-ORO库利用正则表达式。至于最终采用老式的字符串处理方式(使用StringTokenizer,charAt,和substring),还是采用正则表达式,这就有待你自己决定了
原文地址:
http://www.ccw.com.cn/htm/app/aprog/01_7_31_4.asp
posted @
2008-02-28 13:35 CoderDream 阅读(657) |
评论 (0) |
编辑 收藏
如果我们问那些UNIX系统的爱好者他们最喜欢什么,答案除了稳定的系统和可以远程启动之外,十有八九的人会提到正则表达式;如果我们再问他们最头痛的是什么,可能除了复杂的进程控制和安装过程之外,还会是正则表达式。那么正则表达式到底是什么?如何才能真正的掌握正则表达式并正确的加以灵活运用?本文将就此展开介绍,希望能够对那些渴望了解和掌握正则表达式的读者有所助益。
入门简介
简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具。我们可以在几乎所有的基于UNIX系统的工具中找到正则表达式的身影,例如,vi编辑器,Perl或PHP脚本语言,以及awk或sed shell程序等。此外,象JavaScript这种客户端的脚本语言也提供了对正则表达式的支持。由此可见,正则表达式已经超出了某种语言或某个系统的局限,成为人们广为接受的概念和功能。
正则表达式可以让用户通过使用一系列的特殊字符构建匹配模式,然后把匹配模式与数据文件、程序输入以及WEB页面的表单输入等目标对象进行比较,根据比较对象中是否包含匹配模式,执行相应的程序。
举例来说,正则表达式的一个最为普遍的应用就是用于验证用户在线输入的邮件地址的格式是否正确。如果通过正则表达式验证用户邮件地址的格式正确,用户所填写的表单信息将会被正常处理;反之,如果用户输入的邮件地址与正则表达的模式不匹配,将会弹出提示信息,要求用户重新输入正确的邮件地址。由此可见正则表达式在WEB应用的逻辑判断中具有举足轻重的作用。
基本语法
在对正则表达式的功能和作用有了初步的了解之后,我们就来具体看一下正则表达式的语法格式。
正则表达式的形式一般如下:
/love/
其中位于“/”定界符之间的部分就是将要在目标对象中进行匹配的模式。用户只要把希望查找匹配对象的模式内容放入“/”定界符之间即可。为了能够使用户更加灵活的定制模式内容,正则表达式提供了专门的“元字符”。所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。
较为常用的元字符包括: “+”, “*”,以及 “?”。其中,“+”元字符规定其前导字符必须在目标对象? 续出现一次或多次,“*”元字符规定其前导字符必须在目标对象中出现零次或连续多次,而“?”元字符规定其前导对象必须在目标对象中连续出现零次或一次。
下面,就让我们来看一下正则表达式元字符的具体应用。
/fo+/
因为上述正则表达式中包含“+”元字符,表示可以与目标对象中的 “fool”, “fo”, 或者 “football”等在字母f后面连续出现一个或多个字母o的字符串相匹配。
/eg*/
因为上述正则表达式中包含“*”元字符,表示可以与目标对象中的 “easy”, “ego”, 或者 “egg”等在字母e后面连续出现零个或多个字母g的字符串相匹配。
/Wil?/
因为上述正则表达式中包含“?”元字符,表示可以与目标对象中的 “Win”, 或者 “Wilson”,等在字母i后面连续出现零个或一个字母l的字符串相匹配。
除了元字符之外,用户还可以精确指定模式在匹配对象中出现的频率。例如,
/jim{2,6}/
上述正则表达式规定字符m可以在匹配对象中连续出现2-6次,因此,上述正则表达式可以同jimmy或jimmmmmy等字符串相匹配。
在对如何使用正则表达式有了初步了解之后,我们来看一下其它几个重要的元字符的使用方式。
\s:用于匹配单个空格符,包括tab键和换行符;
\S:用于匹配除单个空格符之外的所有字符;
\d:用于匹配从0到9的数字;
\w:用于匹配字母,数字或下划线字符;
\W:用于匹配所有与\w不匹配的字符;
. :用于匹配除换行符之外的所有字符。
(说明:我们可以把\s和\S以及\w和\W看作互为逆运算)
下面,我们就通过实例看一下如何在正则表达式中使用上述元字符。
/\s+/
上述正则表达式可以用于匹配目标对象中的一个或多个空格字符。
/\d000/
如果我们手中有一份复杂的财务报表,那么我们可以通过上述正则表达式轻而易举的查找到所有总额达千元的款项。
除了我们以上所介绍的元字符之外,正则表达式中还具有另外一种较为独特的专用字符,即定位符。定位符用于规定匹配模式在目标对象中的出现位置。
较为常用的定位符包括: “^”, “$”, “\b” 以及 “\B”。其中,“^”定位符规定匹配模式必须出现在目标字符串的开头,“$”定位符规定匹配模式必须出现在目标对象的结尾,\b定位符规定匹配模式必须出现在目标字符串的开头或结尾的两个边界之一,而“\B”定位符则规定匹配对象必须位于目标字符串的开头和结尾两个边界之内,即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾。同样,我们也可以把“^”和“$”以及“\b”和“\B”看作是互为逆运算的两组定位符。举例来说:
/^hell/
因为上述正则表达式中包含“^”定位符,所以可以与目标对象中以 “hell”, “hello”或 “hellhound”开头的字符串相匹配。
/ar$/
因为上述正则表达式中包含“$”定位符,所以可以与目标对象中以 “car”, “bar”或 “ar” 结尾的字符串相匹配。
/\bbom/
因为上述正则表达式模式以“\b”定位符开头,所以可以与目标对象中以 “bomb”, 或 “bom”开头的字符串相匹配。
/man\b/
因为上述正则表达式模式以“\b”定位符结尾,所以可以与目标对象中以 “human”, “woman”或 “man”结尾的字符串相匹配。
为了能够方便用户更加灵活的设定匹配模式,正则表达式允许使用者在匹配模式中指定某一个范围而不局限于具体的字符。例如:
/[A-Z]/
上述正则表达式将会与从A到Z范围内任何一个大写字母相匹配。
/[a-z]/
上述正则表达式将会与从a到z范围内任何一个小写字母相匹配。
/[0-9]/
上述正则表达式将会与从0到9范围内任何一个数字相匹配。
/([a-z][A-Z][0-9])+/
上述正则表达式将会与任何由字母和数字组成的字符串,如 “aB0” 等相匹配。这里需要提醒用户注意的一点就是可以在正则表达式中使用 “()” 把字符串组合在一起。“()”符号包含的内容必须同时出现在目标对象中。因此,上述正则表达式将无法与诸如 “abc”等的字符串匹配,因为“abc”中的最后一个字符为字母而非数字。
如果我们希望在正则表达式中实现类似编程逻辑中的“或”运算,在多个不同的模式中任选一个进行匹配的话,可以使用管道符 “|”。例如:
/to|too|2/
上述正则表达式将会与目标对象中的 “to”, “too”, 或 “2” 相匹配。
正则表达式中还有一个较为常用的运算符,即否定符 “[^]”。与我们前文所介绍的定位符 “^” 不同,否定符 “[^]”规定目标对象中不能存在模式中所规定的字符串。例如:
/[^A-C]/
上述字符串将会与目标对象中除A,B,和C之外的任何字符相匹配。一般来说,当“^”出现在 “[]”内时就被视做否定运算符;而当“^”位于“[]”之外,或没有“[]”时,则应当被视做定位符。
最后,当用户需要在正则表达式的模式中加入元字符,并查找其匹配对象时,可以使用转义符“\”。例如:
/Th\*/
上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。
原文链接:
http://www.yesky.com/181/51681.shtml
posted @
2008-02-28 11:49 CoderDream 阅读(658) |
评论 (0) |
编辑 收藏
在String类中,有四个特殊的方法:
public String[] split(String regex)
- 根据给定的正则表达式的匹配来拆分此字符串。
该方法的作用就像是使用给定的表达式和限制参数 0 来调用两参数 split
方法。因此,结果数组中不包括结尾空字符串。
例如,字符串 "boo:and:foo" 产生带有下面这些表达式的结果:
Regex |
结果 |
: |
{ "boo", "and", "foo" } |
o |
{ "b", "", ":and:f" } |
-
- 参数:
regex
- 定界正则表达式
- 返回: 字符串数组,根据给定正则表达式的匹配来拆分此字符串,从而生成此数组。
- 抛出:
PatternSyntaxException
- 如果正则表达式的语法无效
public String[] split(String regex, int limit)
- 根据匹配给定的正则表达式来拆分此字符串。
此方法返回的数组包含此字符串的每个子字符串,这些子字符串由另一个匹配给定的表达式的子字符串终止或由字符串结束来终止。数组中的子字符串按它们在此字符串中的顺序排列。如果表达式不匹配输入的任何部分,则结果数组只具有一个元素,即此字符串。
limit 参数控制模式应用的次数,因此影响结果数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后项将包含超出最后匹配的定界符的所有输入。如果 n 为非正,则模式将被应用尽可能多的次数,而且数组可以是任意长度。如果 n 为零,则模式将被应用尽可能多的次数,数组可有任何长度,并且结尾空字符串将被丢弃。
例如,字符串 "boo:and:foo" 使用这些参数可生成下列结果:
Regex |
Limit |
结果 |
: |
2 |
{ "boo", "and:foo" } |
: |
5 |
{ "boo", "and", "foo" } |
: |
-2 |
{ "boo", "and", "foo" } |
o |
5 |
{ "b", "", ":and:f", "", "" } |
o |
-2 |
{ "b", "", ":and:f", "", "" } |
o |
0 |
{ "b", "", ":and:f" } |
这种形式的方法调用 str.split(regex, n) 产生与以下表达式完全相同的结果:
Pattern
.compile
(regex).split
(str, n)
参数: regex
- 定界正则表达式 ;limit
- 结果阈值,如上所述
- 返回: 字符串数组,根据给定正则表达式的匹配来拆分此字符串,从而生成此数组
public String replaceAll(String regex, String replacement)
- 使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。
此方法调用的 str.replaceAll(regex, repl) 形式产生与以下表达式完全相同的结果:
Pattern
.compile
(regex).matcher
(str).replaceAll
(repl)
参数: regex
- 用来匹配此字符串的正则表达式
- 返回: 得到的 String
public String replaceFirst(String regex, String replacement)
- 使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的第一个子字符串。
此方法调用的 str.replaceFirst(regex, repl) 形式产生与以下表达式完全相同的结果:
Pattern
.compile
(regex).matcher
(str).replaceFirst
(repl)
参数:regex
- 用来匹配此字符串的正则表达式
- 返回: 得到的 String
这四个方法中都有一个参数为正则表达式(Regular Expression),而不是普通的字符串。
在正则表达式中具有特殊含义的字符
特殊字符
|
描述
|
. |
表示任意一个字符 |
[abc] |
表示a、b或c中的任意一个字符 |
[^abc] |
除a、b和c以外的任意一个字符 |
[a-zA-z] |
介于a到z,或A到Z中的任意一个字符 |
\s |
空白符(空格、tab、换行、换页、回车) |
\S |
非空白符 |
\d |
任意一个数字[0-9] |
\D |
任意一个非数字[^0-9] |
\w |
词字符[a-zA-Z_0-9] |
\W |
非词字符 |
表示字符出现次数的符号
表示次数的符号
|
描述
|
* |
0 次或者多次 |
+ |
1 次或者多次 |
? |
0 次或者 1 次 |
{n} |
恰好 n 次 |
{n, m} |
至少 n 次,不多于 m 次 |
public class RegDemo2 {
/**
* @param args
*/
public static void main(String[] args) {
// 例如,字符串 "boo:and:foo" 产生带有下面这些表达式的结果: Regex 结果
// : { "boo", "and", "foo" }
// o { "b", "", ":and:f" }
String tempStr = "boo:and:foo";
String[] a = tempStr.split(":");
pringStringArray(a);
String[] b = tempStr.split("o");
pringStringArray(b);
System.out.println("--------------------------");
// Regex Limit 结果
// : 2 { "boo", "and:foo" }
// : 5 { "boo", "and", "foo" }
// : -2 { "boo", "and", "foo" }
// o 5 { "b", "", ":and:f", "", "" }
// o -2 { "b", "", ":and:f", "", "" }
// o 0 { "b", "", ":and:f" }
pringStringArray(tempStr.split(":", 2));
pringStringArray(tempStr.split(":", 5));
pringStringArray(tempStr.split(":", -2));
pringStringArray(tempStr.split("o", 5));
pringStringArray(tempStr.split("o", -2));
pringStringArray(tempStr.split("o", 0));
// 字符串 "boo:and:foo"中的所有“:”都被替换为“XX”,输出:booXXandXXfoo
System.out.println(tempStr.replaceAll(":", "XX"));
// 字符串 "boo:and:foo"中的第一个“:”都被替换为“XX”,输出: booXXand:foo
System.out.println(tempStr.replaceFirst(":", "XX"));
}
public static void pringStringArray(String[] s) {
int index = s.length;
for (int i = 0; i < index; i++) {
System.err.println(i + ": " + s[i]);
}
}
}
下面的程序演示了正则表达式的用法:
/**
* discription:
*
* @author CoderDream
*
*/
public class RegularExTester {
/**
* @param args
*/
public static void main(String[] args) {
// 把字符串中的“aaa”全部替換為“z”,打印:zbzcz
System.out.println("aaabaaacaaa".replaceAll("a{3}", "z"));
// 把字符串中的“aaa”、“aa”或者“a”全部替換為“*”,打印:*b*c*
System.out.println("aaabaaca".replaceAll("a{1,3}", "\\*"));
// 把字符串中的數字全部替換為“z”,打印:zzzazzbzzcc
System.out.println("123a44b35cc".replaceAll("\\d", "z"));
// 把字符串中的非數字全部替換為“0”,打印:1234000435000
System.out.println("1234abc435def".replaceAll("\\D", "0"));
// 把字符串中的“.”全部替換為“\”,打印:com\abc\dollapp\Doll
System.out.println("com.abc.dollapp.Doll".replaceAll("\\.", "\\\\"));
// 把字符串中的“a.b”全部替換為“_”,
// “a.b”表示長度為3的字符串,以“a”開頭,以“b”結尾
// 打印:-hello-all
System.out.println("azbhelloahball".replaceAll("a.b", "-"));
// 把字符串中的所有詞字符替換為“#”
// 正則表達式“[a-zA-z_0-9]”等價于“\w”
// 打印:#.#.#.#.#.#
System.out.println("a.b.c.1.2.3.4".replaceAll("[a-zA-z_0-9]", "#"));
System.out.println("a.b.c.1.2.3.4".replaceAll("\\w", "#"));
}
}
值得注意的是,由于“.”、“?”和“*”等在正则表达式中具有特殊的含义,如果要表示字面上的这些字符,必须以“\\”开头。例如为了把字符串“com.abc.dollapp.Doll”中的“.”替换为“\”,应该调用replaceAll("\\.",
\\\\)方法。
Java中的正则表达式类
public interface MatchResult
匹配操作的结果。
此接口包含用于确定与正则表达式匹配结果的查询方法。通过 MatchResult
可以查看匹配边界、组和组边界,但是不能修改
public final class Matcher
-
- extends Object
- implements MatchResult
通过解释 Pattern
对
字符序列
执行匹配操作的引擎。
通过调用模式的 matcher
方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:
每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。
public final class Pattern
-
- extends Object
- implements Serializable
正则表达式的编译表示形式。
指定为字符串的正则表达式必须首先被编译为此类的实例。然后,可将得到的模式用于创建 Matcher
对象,依照正则表达式,该对象可以与任意
字符序列
匹配。执行匹配所涉及的所有状态都驻留在匹配器中,所以多个匹配器可以共享同一模式。
因此,典型的调用顺序是
Pattern p = Pattern.compile
("a*b");
Matcher m = p.matcher
("aaaaab");
boolean b = m.matches
();
在仅使用一次正则表达式时,可以方便地通过此类定义 matches
方法。此方法编译表达式并在单个调用中将输入序列与其匹配。语句
boolean b = Pattern.matches("a*b", "aaaaab");
等效于上面的三个语句,尽管对于重复的匹配而言它效率不高,因为它不允许重用已编译的模式。
此类的实例是不可变的,可供多个并发线程安全使用。Matcher
类的实例用于此目的则不安全。
测试代码:
/**
* discription:Java中正则表达式类的使用
*
* @author CoderDream
*
*/
public class RegDemo {
/**
* @param args
*/
public static void main(String[] args) {
// 檢查字符串中是否含有“aaa”,有返回:true,無返回:false
System.out.println(isHaveBeenSetting("a{3}", "aaabaaacaaa"));
System.out.println(isHaveBeenSetting("a{3}", "aab"));
// 把字符串“abbaaacbaaaab”中的“aaa”全部替換為“z”,打印:abbzbza
System.out.println(replaceStr("a{3}", "abbaaabaaaa", "z"));
}
/**
*
* @param regEx
* 设定的正则表达式
* @param tempStr
* 系统参数中的设定的字符串
* @return 是否系统参数中的设定的字符串含有设定的正则表达式 如果有的则返回true
*/
public static boolean isHaveBeenSetting(String regEx, String tempStr) {
boolean result = false;
try {
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(tempStr);
result = m.find();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 将字符串含有的regEx表达式替换为replaceRegEx
*
* @param regEx
* 需要被替换的正则表达式
* @param tempStr
* 替换的字符串
* @param replaceRegEx
* 替换的正则表达式
* @return 替換好后的字符串
*/
public static String replaceStr(String regEx, String tempStr,
String replaceRegEx) {
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(tempStr);
tempStr = m.replaceAll(replaceRegEx);
return tempStr;
}
}
posted @
2008-02-28 11:40 CoderDream 阅读(2337) |
评论 (0) |
编辑 收藏
实践1:参数以by value方式而非by reference方式传递
实践2:对不变的data和object reference使用final
实践3:缺省情况下所有non-static函数都可被重写
实践4:在array和Vectors之间慎重选择
实践5:多态(polymorphism)优于instanceof
实践6:必要时才使用instanceof
实践7:一旦不需要object reference,就将它设为null
import java.awt.Point;
class PassByValue {
public static void modifyPoint(Point pt, int j) {
pt.setLocation(5, 5); // 1
j = 15;
System.out.println("During modifyPoint " + "pt = " + pt + " and j = " + j);
}
public static void main(String args[]) {
Point p = new Point(0, 0); // 2
int i = 10;
System.out.println("Before modifyPoint " + "p = " + p + " and i = " + i);
modifyPoint(p, i); // 3
System.out.println("After modifyPoint " + "p = " + p + " and i = " + i);
}
}
这段代码在//2处建立了一个Point对象并设初值为(0,0),接着将其值赋予object reference 变量p。然后对基本类型int i赋值10。//3调用static modifyPoint(),传入p和i。modifyPoint()对第一个参数pt调用了setLocation(),将其左边改为(5,5)。然后将第二个参数j赋值为15.当modifyPoint()返回的时候,main()打印出p和i的值。
程序输出如下:
Before modifyPoint p = java.awt.Point[x=0,y=0] and i = 10
During modifyPoint pt = java.awt.Point[x=5,y=5] and j = 15
After modifyPoint p = java.awt.Point[x=5,y=5] and i = 10
这显示modifyPoint()改变了//2 所建立的Point对象,却没有改变int i。在main()之中,i被赋值10.由于参数通过by value方式传递,所以modifyPoint()收到i的一个副本,然后它将这个副本改为15并返回。main()内的原值i并没有受到影响。
对比之下,事实上modifyPoint() 是在与“Point 对象的 reference 的复件”打交道,而不是与“Point对象的复件”打交道。当p从main()被传入modifyPoint()时,传递的是p(也就是一个reference)的复件。所以modifyPoint()是在与同一个对象打交道,只不过通过别名pt罢了。在进入modifyPoint()之后和执行 //1 之前,这个对象看起来是这样:
所以//1 执行以后,这个Point对象已经改变为(5,5)。
Java 关键字 final 用来表示常量数据。例如:
public class Test {
static final int someInt = 10;
//
}
这段代码声明了一个 static 类变量,命名为 someInt,并设其初值为10。
任何试图修改 someInt 的代码都将无法通过编译。例如:
//
someInt = 9; // Error
//
关键字 final 可防止 classes 内的 instance 数据遭到无意间的修改。如果我们想要一个常量对象,又该如何呢?例如:
class Circle {
private double rad;
public Circle(double r) {
rad = r;
}
public void setRadius(double r) {
rad = r;
}
public double radius() {
return rad;
}
}
public class FinalTest {
private static final Circle wheel = new Circle(5.0);
public static void main(String args[]) {
System.out.println("Radius of wheel is " + wheel.radius());
wheel.setRadius(7.4);
System.out.println("Radius of wheel is now " + wheel.radius());
}
}
这段代码的输出是:
Radius of wheel is 5.0
Radius of wheel is now 7.4
在上述第一个示例中,我们企图改变final 数据值时,编译器会侦测出错误。
在第二个示例中,虽然代码改变了 instance变量wheel的值,编译器还是让它通过了。我们已经明确声明wheel为final,它怎么还能被改变呢?
不,我们确实没有改变 wheel 的值,我们改变的是wheel 所指对象的值。wheel 并无变化,仍然指向(代表)同一个对象。变量wheel是一个 object reference,它指向对象所在的heap位置。有鉴如此,下面的代码会怎样?
public class FinalTest {
private static final Circle wheel = new Circle(5.0);
public static void main(String args[]) {
System.out.println("Radius of wheel is " + wheel.radius());
wheel = new Circle(7.4); // 1
System.out.println("Radius of wheel is now " + wheel.radius());
}
}
编译代码,// 1 处出错。由于我们企图改变 final 型变量 wheel 的值,所以这个示例将产生编译错误。换言之,代码企图令wheel指向其他对象。变量wheel是final,因此也是不可变的。它必须永远指向同一个对象。然而wheel所指向的对象并不受关键字final的影响,因此是可变的。
关键字 final 只能防止变量值的改变。如果被声明为 final 的变量是个 object reference,那么该reference不能被改变,必须永远指向同一个对象,但被指的那个对象可以随意改变内部的属性值。
关键字final 在Java中有多重用途,即可被用于instance变量、static变量,也可用于classes或methods,用于类,表示该类不能有子类;用于方法,表示该方法不允许被子类覆盖。
array和Vector的比较
|
支持基本类型
|
支持对象
|
自动改变大小
|
速度快
|
array
|
Yes
|
Yes
|
No
|
Yes
|
Vector
|
No(1.5以上支持)
|
Yes
|
Yes
|
No
|
代码1:instanceof方式
interface Employee {
public int salary();
}
class Manager implements Employee {
private static final int mgrSal = 40000;
public int salary() {
return mgrSal;
}
}
class Programmer implements Employee {
private static final int prgSal = 50000;
private static final int prgBonus = 10000;
public int salary() {
return prgSal;
}
public int bonus() {
return prgBonus;
}
}
class Payroll {
public int calcPayroll(Employee emp) {
int money = emp.salary();
if (emp instanceof Programmer)
money += ((Programmer) emp).bonus(); // Calculate the bonus
return money;
}
public static void main(String args[]) {
Payroll pr = new Payroll();
Programmer prg = new Programmer();
Manager mgr = new Manager();
System.out.println("Payroll for Programmer is " + pr.calcPayroll(prg));
System.out.println("payroll for Manager is " + pr.calcPayroll(mgr));
}
}
依据这个设计,calcPayroll()必须使用instanceof操作符才能计算出正确结果。因为它使用了Employee接口,所以它必须断定Employee对象究竟实际属于哪个class。程序员有奖金而经理没有,所以你必须确定Employee对象的运行时类型。
代码2:多态方式
interface Employee {
public int salary();
public int bonus();
}
class Manager implements Employee {
private static final int mgrSal = 40000;
private static final int mgrBonus = 0;
public int salary() {
return mgrSal;
}
public int bonus() {
return mgrBonus;
}
}
class Programmer implements Employee {
private static final int prgSal = 50000;
private static final int prgBonus = 10000;
public int salary() {
return prgSal;
}
public int bonus() {
return prgBonus;
}
}
class Payroll {
public int calcPayroll(Employee emp) {
// Calculate the bonus. No instanceof check needed.
return emp.salary() + emp.bonus();
}
public static void main(String args[]) {
Payroll pr = new Payroll();
Programmer prg = new Programmer();
Manager mgr = new Manager();
System.out.println("Payroll for Programmer is " + pr.calcPayroll(prg));
System.out.println("Payroll for Manager is " + pr.calcPayroll(mgr));
}
}
在这个设计中,我们为Employee接口增加了 bonus(),从而消除了instanceof的必要性。实现Employee接口的两个 classes:Programmer和Manager,都必须实现salary()和bonus()。这些修改显著简化了calcPayroll()。
import java.util.Vector;
class Shape {
}
class Circle extends Shape {
public double radius() {
return 5.7;
}
//
}
class Triangle extends Shape {
public boolean isRightTriangle() {
// Code to determine if triangle is right
return true;
}
//
}
class StoreShapes {
public static void main(String args[]) {
Vector shapeVector = new Vector(10);
shapeVector.add(new Triangle());
shapeVector.add(new Triangle());
shapeVector.add(new Circle());
//
// Assume many Triangles and Circles are added and removed
//
int size = shapeVector.size();
for (int i = 0; i < size; i++) {
Object o = shapeVector.get(i);
if (o instanceof Triangle) {
if (((Triangle) o).isRightTriangle()) {
//
}
} else if (o instanceof Circle) {
double rad = ((Circle) o).radius();
//
}
}
}
}
这段代码表明在 这种场合下 instanceof 操作符是必需的。当程序从Vector 取回对象,它们属于java.lang.Object。利用instanceof确定对象实际属于哪个class后,我们才能正确执行向下转型,而不至于在运行期抛出异常。
posted @
2008-02-26 17:56 CoderDream 阅读(296) |
评论 (0) |
编辑 收藏
9.1 Java异常处理机制概述
主要考虑的两个问题:(1)如何表示异常情况?(2)如何控制处理异常的流程?
9.1.1 Java异常处理机制的优点
Java语言按照面向对象的思想来处理异常,使得程序具有更好的可维护性。
Java异常处理机制具有以下优点:
- 把各种不同类型的异常情况进行分类,用Java类来表示异常情况,发挥类的可扩展性和可重用性。
- 异常流程的代码和正常流程的代码分离,提供了程序的可读性,简化了程序的结构。
- 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需要抛出异常,由方法调用者来处理它。
9.1.2 Java虚拟机的方法调用栈
如果方法中的代码块可能抛出异常,有如下两种处理方法:
(1)在当前方法中通过try...catch语句捕获并处理异常;
(2)在方法的声明处通过throws语句声明抛出异常。
当Java虚拟机追溯到调用栈的底部的方法时,如果仍然没有找到处理该异常的代码,将按以下步骤处理:
(1)调用异常对象的printStachTrace()方法,打印来自方法调用栈的异常信息。
(2)如果该线程不是主线程,那么终止这个线程,其它线程继续正常运行。如果该线程是主线程,那么整个应用程序被终止。
9.1.3 异常处理对性能的影响
一般来说,影响很小,除非方法嵌套调用很深。
9.2 运用Java异常处理机制
9.2.1 try...catch语句:捕获异常
9.2.2 finally语句:任何情况下都必须执行的代码
主要用于关闭某些流和数据库连接。
9.2.3 thorws子句:声明可能会出现的异常
9.2.4 throw语句:抛出异常
9.2.5 异常处理语句的语法规则
(1)try代码块不能脱离catch代码块或finally代码块而单独存在。try代码块后面至少有一个catch代码块或finally代码块。
(2)try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。
(3)try代码块后面可以只跟finally代码块。
(4)在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。
(5)当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出的异常类对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。
(6)如果一个方法可能出现受检查异常,要么用try...catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
9.2.6 异常流程的运行过程
(1)finally语句不被执行的唯一情况是先执行了用于终止程序的System.exit()方法。
(2)return语句用于退出本方法。
(3)finally代码块虽然在return语句之前被执行,但finally代码块不能通过重新给变量赋值来改变return语句的返回值。
(4)建议不要在finally代码块中使用return语句,因为它会导致以下两种潜在的错误
A:覆盖try或catch代码块的return语句
public class SpecialException extends Exception {
public SpecialException() {
}
public SpecialException(String msg) {
super(msg);
}
}
public class FinallyReturn {
/**
* @param args
*/
public static void main(String[] args) {
FinallyReturn fr = new FinallyReturn();
System.out.println(fr.methodB(1));// 打印100
System.out.println(fr.methodB(2));// 打印100
}
public int methodA(int money) throws SpecialException {
if (--money <= 0) {
throw new SpecialException("Out of money");
}
return money;
}
@SuppressWarnings("finally")
public int methodB(int money) {
try {
return methodA(money);// 可能抛出异常
} catch (SpecialException e) {
return -100;
} finally {
return 100;// 会覆盖try和catch代码块的return语句
}
}
}
B:丢失异常
public class ExLoss {
/**
* @param args
*/
public static void main(String[] args) {
try {
System.out.println(new ExLoss().methodB(1));// 打印100
System.out.println("No Exception");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public int methodA(int money) throws SpecialException {
if (--money <= 0) {
throw new SpecialException("Out of money");
}
return money;
}
@SuppressWarnings("finally")
public int methodB(int money) {
try {
return methodA(money);// 可能抛出异常
} catch (SpecialException e) {
throw new Exception("Wrong");
} finally {
return 100;// 会丢失catch代码块中的异常
}
}
}
9.3 Java异常类
所有异常类的祖先类为java.lang.Throwable类,它的实例表示具体的异常对象,可以通过throw语句抛出。
Throwable类提供了访问异常信息的一些方法,常用的方法包括:
- getMessage() --返回String类型的异常信息。
- printStachTrace()--打印跟踪方法调用栈而获得的详细异常信息。在程序调试阶段,此方法可用于跟踪错误。
public class ExTrace {
/**
* @param args
*/
public static void main(String[] args) {
try {
new ExTrace().methodB(1);
} catch (Exception e) {
System.out.println("--- Output of main() ---");
e.printStackTrace();
}
}
public void methodA(int money) throws SpecialException {
if (--money <= 0) {
throw new SpecialException("Out of money");
}
}
public void methodB(int money) throws Exception {
try {
methodA(money);
} catch (SpecialException e) {
System.out.println("--- Output of methodB() ---");
System.out.println(e.getMessage());
throw new Exception("Wrong");
}
}
}
打印结果:
--- Output of methodB() ---
Out of money
--- Output of main() ---
java.lang.Exception: Wrong
at chapter09.d0903.ExTrace.methodB(ExTrace.java:45)
at chapter09.d0903.ExTrace.main(ExTrace.java:26)
Throwable类有两个直接子类:
- Error类--表示仅靠程序本身无法恢复的严重错误,如内存不足等。
- Exception类--表示程序本身可以处理的异常。
9.3.1 运行时异常
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不会检查它,会编译通过,但运行时如果条件成立就会出现异常。
例如当以下divided()方法的参数b为0,执行“a/b”操作时会出现ArrithmeticException异常,它属于运行时异常,Java编译器不会检查它。
public int divide2(int a, int b) {
return a / b;// 当参数为0,抛出ArrithmeticException
}
下面的程序中的IllegalArgumentException也是运行时异常,divided()方法即没有捕获它,也没有声明抛出它。
public class WithRuntimeEx {
/**
* @param args
*/
public static void main(String[] args) {
new WithRuntimeEx().divide(1, 0);
System.out.println("End");
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除數不能為0");
}
return a / b;
}
}
由于程序代码不会处理运行时异常,因此当程序在运行时出现了这种异常时,就会导致程序异常终止。以上程序的打印结果为:
Exception in thread "main" java.lang.IllegalArgumentException: 除數不能為0
at chapter09.d0903.WithRuntimeEx.divide(WithRuntimeEx.java:29)
at chapter09.d0903.WithRuntimeEx.main(WithRuntimeEx.java:23)
9.3.2 受检查异常
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常(Checked Exception)。这种异常要么catch语句捕获,要么throws子句声明抛出,否则编译出错。
9.3.3 区分运行时异常和受检查异常
受检查异常表示程序可以处理的异常。
运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java编译器不检查这种异常。
9.3.4 区分运行时异常和错误
Error类及其子类表示程序本身无法修复的错误,它和运行时异常的相同之处是:Java编译器都不会检查它们,当程序运行时出现它们,都会终止程序。
两者的不同之处是:Error类及其子类表示的错误通常是由Java虚拟机抛出。
而RuntimeException类表示程序代码中的错误,它是可扩展的,用户可以根据特定的问题领域来创建相关的运行时异常类。
9.4 用户定义异常
9.4.1 异常转译和异常链
public class BaseException extends Exception {
protected Throwable cause = null;
public BaseException() {
}
public BaseException(String msg) {
super(msg);
}
public BaseException(Throwable cause) {
this.cause = cause;
}
public BaseException(String msg, Throwable cause) {
super(msg);
this.cause = cause;
}
public Throwable initCause(Throwable cause) {
this.cause = cause;
return this;
}
public Throwable getCause() {
return cause;
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream outStream) {
printStackTrace(new PrintStream(outStream));
}
public void printStackTrace(PrintWriter writer) {
super.printStackTrace(writer);
if (getCause() != null) {
getCause().printStackTrace(writer);
}
writer.flush();
}
}
9.4.2 处理多样化异常
public class MultiBaseException extends Exception {
protected Throwable cause = null;
private List<Throwable> exceptions = new ArrayList<Throwable>();
public MultiBaseException() {
}
public MultiBaseException(Throwable cause) {
this.cause = cause;
}
public MultiBaseException(String msg, Throwable cause) {
super(msg);
this.cause = cause;
}
public List getException() {
return exceptions;
}
public void addException(MultiBaseException ex) {
exceptions.add(ex);
}
public Throwable initCause(Throwable cause) {
this.cause = cause;
return this;
}
public Throwable getCause() {
return cause;
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream outStream) {
printStackTrace(new PrintStream(outStream));
}
public void printStackTrace(PrintWriter writer) {
super.printStackTrace(writer);
if (getCause() != null) {
getCause().printStackTrace(writer);
}
writer.flush();
}
}
9.5 异常处理原则
9.5.1 异常只能用于非正常情况
9.5.2 为异常提供说明文档
9.5.3 尽可能地避免异常
9.5.4 保持异常的原子性
9.5.5 避免过于庞大的try代码块
9.5.6 在catch子句中指定具体的异常类型
9.5.7 不要在catch代码块中忽略被捕获的异常,可以处理异常、重新抛出异常、进行异常转译
posted @
2008-02-22 17:50 CoderDream 阅读(880) |
评论 (0) |
编辑 收藏
8.1 接口的概念和基本特征
(1)、接口中的成员变量默认都是public、static、final类型的,必须被显式初始化;
(2)、接口中的方法默认都是public、abstract类型的;
(3)、接口中只能包含public、static、final类型的成员变量和public、abstract类型的成员方法;
(4)、接口没有构造方法,不能被实例化;
(5)、一个接口不能实现另一个接口,但可以继承多个其他接口;
(6)、接口必须通过类来实现它的抽象方法。类实现接口的关键字是implements;
(7)、与子类继承抽象父类相似,当类实现了某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类;
(8)、不允许创建接口类型的实例,但允许定义接口类型的引用变量,该变量引用实现了这个接口的类的实例;
(9)、一个类只能继承一个直接的父类,但能实现多个接口。
8.2 比较抽象类与接口
相同点:
- 代表系统的抽象层
- 都不能被实例化
- 都能包含抽象方法
两大区别:
- 在抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现它们,提高代码的可重用性,这是抽象类的优势所在;而接口中只能包含抽象方法;
- 一个类只能继承一个直接的父类,这个父类有可能是抽象类;但一个类可以实现多个接口,这是接口的优势所在。
使用接口和抽象类的原则:
- 用接口作为系统与外界交互的窗口;
- 由于外界使用者依赖系统的接口,并且系统内部会实现接口,因此接口本身必须十分稳定,接口一旦制订,就不允许随意修改,否则会对外界使用者及系统内部都造成影响。
- 用抽象类来定制系统中的扩展点。
8.3 与接口相关的设计模式
8.3.1 定制服务模式
如何设计接口?定制服务模式提出了设计精粒度的接口的原则。
8.3.2 适配器模式
当两个系统之间接口不匹配时,如果处理?适配器模式提供了接口转换方案。
包括继承实现方式和组合实现方式。优先考虑用组合关系来实现适配器。
8.3.3 默认适配器模式
为了简化编程,JDK为MouseListener提供了一个默认适配器MouseAdapter,它实现了MouseListener接口,为所有的方法提供了空的方法体。用户自定义的MyMouseLIstener监听器可以继承MouseAdapter类,在MyMouseListener类中,只需要覆盖特定的方法,而不必实现所有的方法。使用默认适配器可以简化编程,但缺点是该类不能在继承其他的类。
8.3.4 代理模式
下面以房屋出租人的代理为例,介绍代理模式的运用。在下图中,出租人Renter和代理Deputy都具有RenterIFC接口。Tenant类代表租赁人,HouseMarket类代表整个房产市场,它记录了所有房产代理人的信息,出租人从房产市场找到房产代理人。
为了简化起见,假定一个代理人只会为一个出租人做代理,租赁人租房屋rentHouse()的大致过程如下:
- 从房产市场上找到一个房产代理人,即调用HouseMarket对象的findRenter()方法;
- 报出期望的租金价格,征求代理人的意见,即调用Deputy对象的isAgree()方法;
- 代理人的处理方式为:如果租赁人的报价低于出租人的租金价格底线,就立即做出拒绝答复;否则征求出租人的意见,即调用Renter对象的isAgree()方法。
- 出租人的处理方式为:如果租赁人的报价比租金价格底线多100元,就同意出租
- 如果租赁人得到代理人同意的答复,就从存款中取出租金,通知代理人领取租金,即调用Deputy对象的fetchRent()方法
- 代理人通知出租人领取租金,即调用Renter对象的fecthRent()方法。
房屋租赁交易顺利执行的时序图
源代码:
/**
* RetnerIFC 接口,它定义了出租人的两个行为,即决定是否同意按租赁人提出的价格出租房屋,以及收房租
*
* @author XL
*
*/
public interface RenterIFC {
/**
* 是否同意按租赁人提出的价格出租房屋
*
* @param expectedRent
* @return
*/
public boolean isAgree(double expectedRent);
/**
* 收房租
*
* @param rent
*/
public void fetchRent(double rent);
}
/**
* 房屋出租人
*
* @author XL
*
*/
public class Renter implements RenterIFC {
/**
* 房屋租金最低价格
*/
private double rentDeadLine;
/**
* 存款
*/
private double money;
/**
* @param rentDeadLine
* @param money
*/
public Renter(double rentDeadLine, double money) {
super();
System.out.println("New Renter, rentDeadLine: " + rentDeadLine
+ ", saveMoney: " + money);
this.rentDeadLine = rentDeadLine;
this.money = money;
}
/*
* (non-Javadoc)
*
* @see chapter08.d0800.RenterIFC#fetchRent(double)
*/
public void fetchRent(double rent) {
System.out.println("OK, you can use the house.");
money += rent;
}
/*
* (non-Javadoc) 如果租赁人的期望价格比房屋租金最低价格多100元,则同意出租
*
* @see chapter08.d0800.RenterIFC#isAgree(double)
*/
public boolean isAgree(double expectedRent) {
System.out.println("If the money less 100 than the rentDeadLine.");
return expectedRent - this.rentDeadLine > 100;
}
/**
* @return
*/
public double getRentDeadLine() {
return rentDeadLine;
}
}
/**
* 房产代理人
*
* @author XL
*
*/
public class Deputy implements RenterIFC {
private Renter renter;
/**
* 接受代理
*
* @param renter
*/
public void registerRenter(Renter renter) {
System.out.println("OK, I have some business.");
this.renter = renter;
}
public void fetchRent(double rent) {
System.out.println("Get the monty: " + rent);
renter.fetchRent(rent);
}
/*
* (non-Javadoc) 如果租赁人的期望价格低于房屋租金最低价格,则不同意出租 否则请示出租人的意见
*
* @see chapter08.d0800.RenterIFC#isAgree(double)
*/
public boolean isAgree(double expectedRent) {
//
if (expectedRent < renter.getRentDeadLine()) {
System.out.println("Sorry, you can't rent the house.");
return false;
} else {
System.out.println("Let me ask the renter.");
return renter.isAgree(expectedRent);
}
}
}
import java.util.HashSet;
import java.util.Set;
/**
* @author XL
*
*/
public class HouseMarket {
private static Set<RenterIFC> renters = new HashSet<RenterIFC>();
public static void registerRenter(RenterIFC deputy) {
System.out.println("A new man has registered!");
renters.add(deputy);
}
public static RenterIFC findRenter() {
System.out.println("Let's find something!");
return (RenterIFC) renters.iterator().next();
}
}
/**
* 房屋租赁人
*
* @author XL
*
*/
public class Tenant {
private double money;
public Tenant(double money) {
//
System.out.println("New Tenant!");
System.out.println("I have " + money);
this.money = money;
}
public boolean rentHouse(double expectedRent) {
// 从房地产市场找到一个房产代理人
RenterIFC renter = HouseMarket.findRenter();
System.out.println("I can offer " + expectedRent);
// 如果代理人不同意预期的租金价格,就拉倒,否则继续执行
if (!renter.isAgree(expectedRent)) {
System.out.println("I can't offer any more!");
return false;
}
// 从存款中取出预付租金
money -= expectedRent;
System.out.println("OK, get the money, " + expectedRent);
// 把租金交给房产代理人
renter.fetchRent(expectedRent);
return true;
}
}
/**
* @author XL
*
*/
public class AppMain {
/**
* @param args
*/
public static void main(String[] args) {
// 创建一个房屋出租人,房屋租金最低价格为2000元,存款1万元
Renter renter = new Renter(2000, 10000);
// 创建一个房产代理人
Deputy deputy = new Deputy();
// 房产代理人到房产市场登记
HouseMarket.registerRenter(deputy);
// 建立房屋出租人和房产代理人的委托关系
deputy.registerRenter(renter);
// 创建一个房屋租赁人,存款为2万元
Tenant tenant = new Tenant(20000);
// 房屋租赁人试图租赁期望租金为1800元的房屋,遭到房产代理人拒绝
tenant.rentHouse(1800);
// 房屋租赁人试图租赁期望租金为2300元的房屋,租房成功
tenant.rentHouse(2300);
}
}
输出结果:
New Renter, rentDeadLine: 2000.0, saveMoney: 10000.0
A new man has registered!
OK, I have some business.
New Tenant!
I have 20000.0
Let's find something!
I can offer 1800.0
Sorry, you can't rent the house.
I can't offer any more!
Let's find something!
I can offer 2300.0
Let me ask the renter.
If the money less 100 than the rentDeadLine.
OK, get the money, 2300.0
Get the monty: 2300.0
OK, you can use the house.
8.3.5 标识类型模式
标识类型接口没有任何方法,仅代表一种抽象类型。
在JDK中,有如下两个典型的标识类型接口:
- java.io.Serializable接口:实现该接口的类可以被序列化。
- java.io.Remote接口:实现该接口的类的实例可以作为远程对象。
8.3.6 常量接口模式
posted @
2008-02-19 18:03 CoderDream 阅读(395) |
评论 (0) |
编辑 收藏