java.util.regex包支持正则表达式处理。作为在此处使用的术语,正则表达式是描述字符序列的一串字符。这种通过描述被称为模式,可以用于在其他字符序列中查找匹配。正则表达式可以指定通配符、一组字符和各种量词。因此,可以指定一种通用形式的正则表达式,以匹配多种不同的特定字符系列。
正则表达式处理由两个类支持:Pattern和Matcher。这两个类协同工作:使用Pattern类定义正则表达式,使用Matcher类在其他序列中匹配模式。
1 Pattern 类
Pattern类没有定义构造函数。相反,模式是通过compile()工厂方法创建的。该方法的一种形式如下所示:
static Pattern compile(String pattern)
其中,pattern是希望使用的正则表达式。compile()方法将pattern中的字符串转换成一种模式,Matcher可以使用这种模式进行模式匹配。该方法返回包含模式的Pattern对象。
一旦创建Pattern对象,就可以使用Pattern对象创建Matcher对象。这是通过Pattern类定义的matcher()工厂来完成的,该方法如下所示:
Matcher matcher(CharSequence str)
其中,str是将要用于匹配模式的字符序列,又称为输入序列。CharSequence是接口,定义了一组只读字符。String以及其他类实现了该接口,因此可以向matcher()方法传递字符串。
2 Matcher 类
Matcher类没有构造函数。相反,正如刚才所解释的,通过调用Pattern定义的matcher()工厂方法来创建Matcher对象。一旦创建Matcher对象,就可以使用Matcher对象的方法来执行各种模式匹配操作。
最简单的模式匹配方法是matches()。该方法简单地确定字符序列是否与模式匹配,如下所示:
boolean matches()
如果字符序列与模式相匹配,就返回true;否则返回false。需要理解的是整个序列必须与模式相匹配,而不仅仅是子序列与模式相匹配。
为了确定输入序列的子序列是否与模式相匹配,需要使用find()方法,该方法的一个版本如下所示:
boolean find()
如果存在匹配的子序列,就返回true;否则返回false。可以重复调用这个方法,以查找所有匹配的子序列。对find()方法的每次调用,都是从上一次离开的位置开始。
可以通过调用group()方法来获得包含最后一个匹配序列的字符串,该方法的一种形式如下所示:
String group()
该方法返回匹配的字符串。如果不存在匹配,就抛出IllegalException异常。
可以通过调用start()方法,获得输入序列中当前匹配的索引。通过end()方法,可以获得当前匹配序列末尾之后下一个字符的索引。这两个方法如下所示:
int start();
int end();
如果不存在匹配序列,这两个方法都会抛出IllegalStateException异常。
可以通过调用replaceAll()方法,使用另一个序列替换所有匹配的序列,该方法入下所示:
String replaceAll(String newStr)
其中,newStr指定了新的字符序列,该序列将用于替换与模式相匹配的序列。更新后的输入序列作为字符串返回。
3 正则表达式的语法
一般而言,正则表达式是由常规字符、字符类(一组字符)、通配符以及量词构成的。常规字符根据自身进行匹配。因此,如果模式由"xy"构造而成,那么匹配该模式的唯一输入序列是"xy"。诸如换行符、制表符这类字符,使用标准的转义序列指定,标准转义序列以"“开头。例如,换行符通过”\n"来指定。在正则表达式中,常规字符也被称作字面值。
字符类是一组字符。通过在方括号之间放置字符,可以指定字符类。例如,类[wxyz]匹配w、x、y、或z。为了指定一组排除性字符,可以在字符前使用"^"。例如,类[^wxyz]匹配除w、x、y以及z之外的字符。可以使用连字符指定字符范围。例如,为了指定匹配数字1到9的字符类,可以使用[1-9]。
通配符是点(.),可以匹配任意字符。因此,由"."构成的模式将匹配以下(以及其他)输入序列:“A”、“a”、"x"等。
量词决定表达式将被匹配的次数。量词如下所示:
- + :匹配一次或多次
- . :匹配零次或多次
- ?:匹配零次或一次
例如,模式"x+“将与"x”、"xx"以及"xxx"等匹配。
另外一点:一般来说,如果指定的表达式无效,将会抛出PatternSyntaxException异常。
4 演示模式匹配
要理解正则表达式模式匹配操作的原理,最好的方法是举一些例子。第一个例子如下所示,使用字面值模式查找匹配:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//A simple pattern matching demo.
class RegExpr {
public static void main(String[] args) {
Pattern pat;
Matcher mat;
boolean found;
pat = Pattern.compile("Java");
mat = pat.matcher("Java");
found = mat.matches();//check for a match
System.out.println("Testing Java against Java.");
if (found) {
System.out.println("Matches");
} else {
System.out.println("No Match");
}
System.out.println();
System.out.println("Testing Java against Java 8.");
mat = pat.matcher("Java 8");
found = mat.matches();//check for a match
if (found) {
System.out.println("Matches");
} else {
System.out.println("No Match");
}
/**
* 输出:
* Testing Java against Java.
* Matches
*
* Testing Java against Java 8.
* No Match
*/
}
}
下面详细分析这个程序。程序首先创建创建包含序列"Java"的模式。接下来,为该模式创建Matcher对象,该对象具有输入序列"Java"。然后,调用matches()方法来确定输入序列是否与模式匹配。因为输入序列和模式相同,所以matches()方法返回true。接下来,使用输入序列"Java 8"创建新的Matcher对象,并再次调用matches()方法。对于这种情况,模式和输入序列不同,没有发现匹配。请记住,只有当输入序列与模式精确匹配时,matches()方法才返回true;只有子序列匹配时,不会返回true。
可以使用find()方法来确定输入序列是否包含与模式匹配的子序列。分析下面的程序:
//Use find() to find a subsequence.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr2 {
public static void main(String[] args) {
Pattern pat = Pattern.compile("Java");
Matcher mat = pat.matcher("Java 8");
System.out.println("Looking for Java in Java 8.");
if(mat.find()){
System.out.println("subsequence found");
}else{
System.out.println("No Match");
}
}
/**
* 输出:
* Looking for Java in Java 8.
* subsequence found
*/
}
在这个例子中,find()方法找到了子序列"Java"。
可以使用find()方法查找输入序列中模式重复出现的次数,因为每次find()调用,都是从上一次离开的地方开始查找。例如,下面的程序可以找出与模式"test"的两次匹配。
//Use find() to find multiple subsequence.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr3 {
public static void main(String[] args) {
Pattern pat = Pattern.compile("test");
Matcher mat = pat.matcher("test 1 2 3 test");
while ((mat.find())){
System.out.println("test found at index "+mat.start());
}
}
/**
* 输出:
* test found at index 0
* test found at index 11
*/
}
正如输出所显示的,找到了两个匹配。程序使用start()方法获取每个匹配的索引。
1. 使用通配符与量词
尽管前面的程序显示了使用Pattern和Matcher类的通用技术,但是这些程序没有展示出它们的强大功能。只有在用到通配符和量词时,才能真正发现正则表达式处理带来的好处。该例使用量词"+"来匹配任意长度的"W"序列。
//Use a quantifier.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr4 {
public static void main(String[] args) {
Pattern pat = Pattern.compile("W+");
Matcher mat = pat.matcher("W WW WWW");
while (mat.find()){
System.out.println("Match: "+mat.group());
}
}
/*
输出:
Match: W
Match: WW
Match: WWW
*/
}
正如输出所显示的,正则表达式模式"W+“能匹配任意长度的W序列。
下一个程序使用通配符创建了一个模式,该模式将匹配以e开始并以d结束的任意序列。为了达到这一目的,使用点通配符和”+";量词:
//Use wildcard and quantifier.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr5 {
public static void main(String[] args) {
Pattern pat = Pattern.compile("e.+d");
Matcher mat = pat.matcher("extend cup end table");
while ((mat.find())){
System.out.println("Match: "+mat.group());
}
}
/*
Match: extend cup end
*/
}
只发现一个匹配,并且是以e开头、以d结尾的最长序列。我们可能会期望得到两个匹配:“extend"和"end”。发现更长序列的原因是,默认情况下,find()方法会匹配适合模式的最长序列,这被称为"贪婪行为"。可以通过为模式添加"?“量词来指定"胁迫行为”,如下面的版本所示,这将导致获得最短的匹配模式:
//Use the ? quantifier.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr6 {
public static void main(String[] args) {
//Use reluctant matching behavior.
Pattern pat = Pattern.compile("e.+?d");
Matcher mat = pat.matcher("extend cup end table");
while ((mat.find())) {
System.out.println("Match: " + mat.group());
}
}
/*
Match: extend
Match: end
*/
}
正如输出所显示的,模式"e.+?d"将会匹配以e开始并且以d结尾的最短序列。因此,找到了两个匹配。
2. 使用字符类
有时会希望以任意顺序包含一个或多个字符的任意序列。例如,为了匹配整个单词,希望匹配字母表的任意序列。实现这一目的的最简单方法是使用字符类。字符类定义了一组字符。字符类是通过在方括号中放置希望匹配的字符来创建的。例如,为了匹配从a到z的小写字母,可以使用[a-z]。下面的程序演示了这种技术:
//Use a character class.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr7 {
public static void main(String[] args) {
//Match lowercase words.
Pattern pat = Pattern.compile("[a-z]+");
Matcher mat = pat.matcher("this is a test.");
while ((mat.find())){
System.out.println("Match: "+mat.group());
}
}
/*
输出:
Match: this
Match: is
Match: a
Match: test
*/
}
3. 使用replaceAll()方法
Matcher类提供的replaceAll()方法用于使用正则表达式执行强大的搜索和替换操作。例如,下面的程序使用"Eric"替换所有以"Jon"开头的序列:
//Use replaceAll()
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpr8 {
public static void main(String[] args) {
String str="John Jonathan Frank Ken Todd";
Pattern pat = Pattern.compile("Jon.*? ");
Matcher mat =pat.matcher(str);
System.out.println("Original sequence: "+str);
str= mat.replaceAll("Eric ");
System.out.println("Modified sequence: "+str);
}
/*
输出:
Original sequence: John Jonathan Frank Ken Todd
Modified sequence: John Eric Frank Ken Todd
*/
}
因为正则表达式"Jon.*? "匹配以Jon开头、后面跟随零个或多个字符并以空格结尾的任意字符串,所以可以用于匹配Jon和Jonathan,并使用Eric替换它们。如果不使用模式匹配功能,这种替换很难实现。
4. 使用split()方法
使用Pattern类定义的split()方法,可以将输入序列化简化成单个标记。split()方法的一种形式如下所示:
String[] split(CharSequence str)
该方法处理str传入的输入序列,根据模式指定的定界符将输入序列简化成标记:
//Use split().
import java.util.regex.Pattern;
class RegExpr9 {
public static void main(String[] args) {
//Match lowercase words.
Pattern pat = Pattern.compile("[ ,.!]");
String strs[] = pat.split("one two,alpha9 12!done.");
for (int i=0;i<strs.length;i++){
System.out.println("Next token: "+strs[i]);
}
}
/*
输出:
Next token: one
Next token: two
Next token: alpha9
Next token: 12
Next token: done
*/
}
正如输出所显示的,这个输入序列被简化成单个编辑。注意不包含定界符。
5 模式匹配的两个选项
尽管前面描述的模式匹配技术提供了最强大的灵活性和功能,但是还有另外两种技术在某些环境下可能会有用。如果只需要进行一次模式匹配,可以使用Pattern类定义的matches()方法。该方法如下所示:
static boolean matches(String pattern,CharSequence str)
如果pattern与str匹配,就返回true;否则返回false。这个方法自动编译pattern,然后查找匹配。如果需要重复使用相同的模式,那么相对于前面模式的先编译模式、后使用Matcher类定义的方法进行匹配,使用matches()方法的效率更低。
也可以使用String类实现的matches()方法执行模式匹配,该方法如下所示:
boolean matches(String pattern)
如果调用字符串与pattern中的正则表达式相匹配,matches()方法就返回true,否则返回false。