本教程的前半部分先容了正则表达式和正则表达式API。您理解了Pattern该类,然后通过演斧正则表达式的示例,从基本模式匹配笔墨字符串到更繁芜的匹配利用范围,边界匹配器和量词。
在第2部分,我们将拿起我们离开的地方,探索与干系联的方法Pattern,Matcher以及PatternSyntaxException类。您还将先容两种利用正则表达式来简化常见编码任务的工具。第一个提取代码中的注释用于文档目的。第二个是用于实行词法剖析的可重用库,它是汇编器,编译器和类似软件的主要组成部分。
探索正则表达式API

Pattern,Matcher并且PatternSyntaxException是构成正则表达式API的三个类。每个类供应可用于将正则表达式集成到代码中的方法。
图案方法
Pattern该类的实例描述了一个编译的正则表达式,也称为模式。编译正则表达式以增加模式匹配操作期间的性能。以下static方法支持编译。
lPattern compile(String regex)将regex内容编译成存储在新Pattern工具中的中间表示。该方法或者在成功时返回工具的引用,或者PatternSyntaxException如果检测到无效的语法,则抛出该方法regex。此Matcher工具利用或返回的任何工具都Pattern遵守各种默认设置,例如区分大小写的搜索。作为示例,Pattern p = Pattern.compile(\"大众(?m)^\\.\"大众);创建一个Pattern存储正则表达式的编译表示的工具,以匹配以句点字符开头的所有行。
lPattern compile(String regex, int flags)完成与...相同的任务Pattern compile(String regex),但可以阐明为flags:一个有点包含OROR的标志常量位值凑集。Pattern声明CANON_EQ,CASE_INSENSITIVE,COMMENTS,DOTALL,LITERAL,MULTILINE,UNICODE_CASE,UNICODE_CHARACTER_CLASS,和UNIX_LINES可以按位或运算在一起(例如,常数CASE_INSENSITIVE | DOTALL),并通报给flags。
除CANON_EQ,LITERAL和UNICODE_CHARACTER_CLASS,这些常数是嵌入标志表达式替代,其被证明在第1部分所述的Pattern compile(String regex, int flags)方法引发java.lang.IllegalArgumentException,当它检测到一个标志常数比由下式定义的其它Pattern常数。例如,Pattern p = Pattern.compile(\"大众^\\.\公众, Pattern.MULTILINE);等同于前面的例子,个中Pattern.MULTILINE常量和(?m)嵌入式标志表达式完成相同的任务。
有时您将须要得到已编译到Pattern工具中的原始正则表达式字符串的副本,以及它正在利用的标志。您可以通过调用以下方法来实行此操作:
lString pattern()返回已编译到Pattern工具中的原始正则表达式字符串。
lint flags()返回Pattern工具的标志。
获取Pattern工具后,常日会利用它来获取Matcher工具,以便您可以实行模式匹配操作。在Matcher matcher(Charsequence input)创建Matcher该匹配供应的工具input文本对给定Pattern工具的已编译正则表达式。当被调用时,它返回对该Matcher工具的引用。例如,为变量引用的工具Matcher m = p.matcher(args[1]);返回一个。MatcherPatternp
一次性比赛
Pattern的static boolean matches(String regex, CharSequence input)方法保存你的麻烦创建的Pattern和Matcher一次性的模式匹配工具。该input匹配时返回true regex; 否则返回false。如果regex包含语法缺点,该方法将抛出一个PatternSyntaxException。例如,System.out.println(Pattern.matches(\"大众[a-z[\\s]]\公众, \"大众all lowercase letters and whitespace only\"大众));输出true,表示仅显示空格字符和小写字母all lowercase letters and whitespace only。
分割笔墨
大多数开拓职员已经编写代码来将输入文本分解成其组成部分,例如将基于文本的员工记录转换为一组字段。Pattern通过一种文本分割方法,可以更快地处理这个问题:
lString[] split(CharSequence text, int limit)分割工具模式的text匹配,Pattern并将结果返回到数组中。每个条款通过模式匹配(或文本结尾)指定与下一个文本序列分离的文本序列。所有数组条款都按照它们涌现的顺序存储text。
在这种方法中,数组条款标数量取决于limit哪个,它还掌握发生的匹配次数:
Ø正值意味着最多limit - 1匹配被考虑,并且数组的长度不大于limit条款。
Ø负值表示考虑所有可能的匹配,并且数组可以是任何长度。
Ø零表示考虑所有可能的匹配,数组可以有任何长度,并且尾随的空字符串被丢弃。
lString[] split(CharSequence text) 调用以前的方法为零作为限定,并返回方法调用的结果。
以下是如何split(CharSequence text)处理将员工记录分割成名称,年事,街道地址和人为的字段组件的任务:
Pattern p = Pattern.compile(\"大众,\\s\"大众);
String[] fields = p.split(\"大众John Doe, 47, Hillsboro Road, 32000\"大众);
for (int i = 0; i < fields.length; i++)
System.out.println(fields[i]);
上面的代码指定一个正则表达式,匹配一个逗号字符,紧随着一个单一空格字符。以下是输出:
John Doe
47
Hillsboro Road
32000
模式谓词和Streams API
Java 8引入了Predicate<String> asPredicate()方法Pattern。此方法创建用于模式匹配的谓词(布尔值函数)。下面的代码演示asPredicate():
List<String> progLangs = Arrays.asList(\"大众apl\"大众, \"大众basic\公众, \"大众c\"大众, \公众c++\"大众, \"大众c#\"大众, \"大众cobol\"大众,\"大众java\公众, \公众javascript\公众, \"大众perl\公众, \"大众python\"大众, \"大众scala\"大众);
Pattern p = Pattern.compile(\公众^c\"大众);
progLangs.stream().filter(p.asPredicate()).forEach(System.out::println);
此代码创建一个编程措辞名称列表,然后编译一个模式,以匹配所有以小写字母开头的名称c。上面的末了一行得到了一个以列表为源的顺序流。它安装一个利用asPredicate()布尔函数的过滤器,当一个名称开头时返回true c,并且迭代流,将匹配的名称输出到标准输出。
末了一行相称于以下传统循环,您可能会从第1部分的RegexDemo运用程序中记住:
for (String progLang: progLangs)
if (p.matcher(progLang).find())
System.out.println(progLang);
匹配方法
Matcher该类的实例描述了通过阐明Pattern编译的正则表达式来对字符序列实行匹配操作的引擎。Matcher工具支持不同种类的模式匹配操作:
lboolean find()扫描下一个匹配的输入文本。该方法可以在给定文本的开始处或者在前一个匹配之后的第一个字符处开始扫描。后一个选项只有在以前的方法调用返回true并且匹配器未被重置时才可能。在任何一种情形下,当创造匹配时返回布尔值true。你可以RegexDemo从第1部分中找到这个方法的例子。
lboolean find(int start)重置匹配器并扫描下一个匹配的文本。扫描从指定的索引开始start。创造匹配时返回布尔值true。例如,m.find(1);从索引开始扫描文本1。(索引0被忽略)如果start包含负值或超过匹配器文本长度的值,则此方法将抛出java.lang.IndexOutOfBoundsException。
lboolean matches()考试测验将全体文本与模式进行匹配。当全体文本匹配时,此方法返回true。例如,Pattern p = Pattern.compile(\公众\\w\"大众); Matcher m = p.matcher(\"大众abc!\公众); System.out.println(p.matches());输出,false由于!符号不是字符。
lboolean lookingAt()考试测验将给定文本与模式进行匹配。当任何文本匹配时,此方法返回true。不同的是matches(),全体文本不须要匹配。例如,Pattern p = Pattern.compile(\公众\\w\"大众); Matcher m = p.matcher(\"大众abc!\"大众); System.out.println(p.lookingAt());输出,true由于abc!文本的开头仅由字符字符组成。
与Pattern工具不同,Matcher工具记录状态信息。偶尔,您可能须要重新设置匹配器以在实行模式匹配后打消该信息。以下方法重置匹配器:
lMatcher reset()重置匹配器的状态,包括匹配器的附加位置(打消为零)。下一个模式匹配操作从匹配器文本的开头开始。Matcher返回对当前工具的引用。例如,m.reset();重置引用的匹配器m。
lMatcher reset(CharSequence text)重置匹配器的状态并将匹配器的文本设置为text。下一个模式匹配操作从匹配器新文本的开始开始。Matcher返回对当前工具的引用。例如,m.reset(\"大众new text\"大众);重置m参考匹配器,并将其指定new text为匹配器的新文本。
附加笔墨
匹配器的追加位置标识匹配器文本的起始位置java.lang.StringBuffer object。以下方法利用追加位置:
lMatcher appendReplacement(StringBuffer sb, String replacement)读取匹配器的文本字符并将其附加到被sb引用的StringBuffer工具。该方法在上一个模式匹配之前的末了一个字符之后停滞读取。接下来,该方法将replacement参考String工具中的字符附加到StringBuffer工具。(该replacement字符串可能包含对上一个匹配期间捕获的文本序列的引用,通过美元符号($)和捕获组号。)末了,该方法将匹配器的追加位置设置为末了一个匹配字符的索引加1,然后返回对当前匹配器的引用。
该Matcher appendReplacement(StringBuffer sb, String replacement)方法java.lang.IllegalStateException在匹配器尚未匹配时或当前一个匹配考试测验失落败时抛出。IndexOutOfBoundsExceptionreplacement
lStringBuffer appendTail(StringBuffer sb)将所有文本附加到StringBuffer工具并返回该工具的引用。在对该appendReplacement(StringBuffer sb, String replacement)方法进行末了调用之后,调用appendTail(StringBuffer sb)将剩余文本复制到StringBuffer工具。
捕获组
从第1部分回忆一个捕获组是由括号metacharacters(( ))包围的字符序列。这个布局的目的是保存一个匹配的字符,以便在模式匹配期间进行往后的调用。捕捉组中的所有字符在模式匹配期间被视为单个单元。
下面的代码调用appendReplacement(StringBuffer sb, String replacement),并appendTail(StringBuffer sb)改换所有涌现cat与caterpillar所供应的笔墨:
Pattern p = Pattern.compile(\"大众(cat)\"大众);
Matcher m = p.matcher(\"大众one cat, two cats, or three cats on a fence\"大众);
StringBuffer sb = new StringBuffer();
while (m.find())
m.appendReplacement(sb, \"大众$1erpillar\"大众);
m.appendTail(sb);
System.out.println(sb);
在更换文本中放置捕获组和对捕获组的引用,指示程序erpillar在每次cat匹配之后插入。上述代码产生以下输出:
one caterpillar, two caterpillars, or three caterpillars on a fence
更换笔墨
Matcher供应了一对补充的文本更换方法appendReplacement(StringBuffer sb, String replacement)。这些方法可以让您更换第一场比赛或所有比赛:
lString replaceFirst(String replacement)重置匹配器,创建一个新String工具,将所有匹配器的文本字符(最多匹配到第一个匹配项)replacement复制到字符串,将字符附加到字符串,将剩余字符复制到字符串,并返回String工具。(replacement字符串可能包含对上一个匹配期间捕获的文本序列的引用,通过美元符号和捕获组号)。
lString replaceAll(String replacement)以类似的办法操作replaceFirst(String replacement),但是更换所有与replacement字符的匹配。
该\s+正则表达式的检测在输入文本中的空缺字符的一个或多个事宜。下面我们利用这个正则表达式,并调用该replaceAll(String replacement)方法来删除重复的空格:
Pattern p = Pattern.compile(\"大众\\s+\"大众);
Matcher m = p.matcher(\"大众Remove the \t\t duplicate whitespace. \公众);
System.out.println(m.replaceAll(\"大众 \"大众));
这是输出:
Remove the duplicate whitespace.
捕捉面向群体的方法
RegexDemo运用程序的源代码包括m.group()方法调用。该group()方法是几种捕获面向群体的Matcher方法之一:
lint groupCount()以匹配器模式返回捕获组的数量。该计数不包括表示全体模式的分外捕获组号码0。
lString group()返回上一个匹配的字符。此方法返回一个空字符串,以指示与空字符串成功匹配。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lString group(int group)类似于以前的方法,除了它返回由group指定的捕获组号记录的先前匹配的字符。把稳group(0)相称于group()。如果在模式中没有存在具有指定组号的捕获组,则该方法抛出IndexOutOfBoundsException。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时,它会抛出。
lString group(String name)返回由named捕获组记录的先前匹配的字符。如果在给定的模式中没有捕获组name,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
以下示例演示了groupCount()和group(int group)方法:
Pattern p = Pattern.compile(\"大众(.(.(.)))\公众);
Matcher m = p.matcher(\"大众abc\"大众);
m.find();
System.out.println(m.groupCount());
for (int i = 0; i <= m.groupCount(); i++)
System.out.println(i + \"大众: \"大众 + m.group(i));
它产生以下输出:
3
0: abc
1: abc
2: bc
3: c
匹配位置法
Matcher 供应了几种返回匹配的开始和结束索引的方法:
lint start()返回上一个比赛的开始索引。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lint start(int group)类似于之前的方法,除了它返回与group指定的捕获组干系联的上一个匹配的开始索引。如果没有指定捕获组号的捕获组存在于模式中,IndexOutOfBoundsException则抛出。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lint start(String name)类似于之前的方法,除了它返回与name指定的捕获组干系联的上一个匹配的开始索引。如果没有指定的捕获组name在模式中存在,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lint end()返回上一个匹配字符的索引加上一个匹配的匹配。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lint end(int group)类似于之前的方法,除了它返回与group指定的捕获组干系联的上一个匹配的结束索引。如果与指定的组数没有捕获组在图案存在,IndexOutOfBoundsException被抛出。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
lint end(String name)类似于之前的方法,除了它返回与name指定的捕获组干系联的上一个匹配的结束索引。如果没有指定的捕获组name在模式中存在,IllegalArgumentException则抛出。IllegalStateException当匹配器尚未考试测验匹配或之前的匹配操作失落败时抛出。
以下示例演示了两种匹配位置方法来报告捕获组号2的开始/结束匹配位置:
Pattern p = Pattern.compile(\公众(.(.(.)))\公众);
Matcher m = p.matcher(\公众abcabcabc\公众);
while (m.find())
{
System.out.println(\"大众Found \公众 + m.group(2));
System.out.println(\"大众 starting at index \公众 + m.start(2) +
\"大众 and ending at index \"大众 + (m.end(2) - 1));
System.out.println();
}
此示例天生以下输出:
Found bc
starting at index 1 and ending at index 2
Found bc
starting at index 4 and ending at index 5
Found bc
starting at index 7 and ending at index 8
PatternSyntaxException方法
PatternSyntaxException该类的实例描述了正则表达式中的语法缺点。此非常从抛出Pattern的compile()和matches()方法,以及通过下面的布局被布局:
PatternSyntaxException(String desc, String regex, int index)
布局函数存储指定description,regex和index个中在发生语法缺点regex。将index被设置为-1当语法缺点的位置是未知的。
虽然您可能永久不须要实例化PatternSyntaxException,但在创建格式化的缺点时,您将须要提取上述值。调用以下方法来完成此任务:
lString getDescription() 返回语法缺点的描述。
int getIndex() 返回发生语法缺点的近似索引(在正则表达式中),或者索引未知时返回-1。
lString getPattern() 返回缺点的正则表达式。
此外,inherited String getMessage()方法返回一个多行字符串,个中包含从上述方法返回的值以及模式中语法缺点位置的可视指示。
什么构成语法缺点?以下是一个例子:
java RegexDemo (?itree Treehouse
在这种情形下,我们未能)在嵌入式标志表达式中指定圆括号metacharacter()。该缺点导致以下输出:
regex = (?itree
input = Treehouse
Bad regex: Unknown inline modifier near index 3
(?itree
^
Description: Unknown inline modifier
Index: 3
Incorrect pattern: (?itree
利用Regex API构建有用的正则表达式运用程序
正则表达式可让您创建强大的文本处理运用程序。本节先容一些有用的运用程序,约请您进一步探索Regex API中的类和方法。第二个运用程序还先容了Lexan:一个用于实行词法剖析的可重用库。
用于文档的正则表达式
文档是开拓专业质量软件的必要任务之一。幸运的是,正则表达式可以帮助文档的许多方面。清单1中的代码将包含单行和多行C措辞注释的行从一个源文件中提取到另一个源文件。注释必须位于单行上才能使代码生效:
清单1.提取注释
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class ExtCmnt
{
public static void main(String[] args)
{
if (args.length != 2)
{
System.err.println(\"大众usage: java ExtCmnt infile outfile\"大众);
return;
}
Pattern p;
try
{
// The following pattern defines multiline comments that appear on the
// same line (e.g., / same line /) and single-line comments (e.g., //
// some line). The comment may appear anywhere on the line.
p = Pattern.compile(\"大众./\\.\\/|.//.$\"大众);
}
catch (PatternSyntaxException pse)
{
System.err.printf(\"大众Regex syntax error: %s%n\"大众, pse.getMessage());
System.err.printf(\公众Error description: %s%n\"大众, pse.getDescription());
System.err.printf(\公众Error index: %s%n\"大众, pse.getIndex());
System.err.printf(\公众Erroneous pattern: %s%n\公众, pse.getPattern());
return;
}
try (FileReader fr = new FileReader(args[0]);
BufferedReader br = new BufferedReader(fr);
FileWriter fw = new FileWriter(args[1]);
BufferedWriter bw = new BufferedWriter(fw))
{
Matcher m = p.matcher(\"大众\"大众);
String line;
while ((line = br.readLine()) != null)
{
m.reset(line);
if (m.matches()) / entire line must match /
{
bw.write(line);
bw.newLine();
}
}
}
catch (IOException ioe)
{
System.err.println(ioe.getMessage());
return;
}
}
}
清单1的main()方法首先验证其命令行,然后编译正则表达式以将单行和多行注释定位到Pattern工具中。假设没有PatternSyntaxException涌现,main()打开源文件并创建目标文件,获取匹配器以匹配每个读取行与模式,并逐行读取源文件的内容。对付每一行,匹配器考试测验将该行与注释模式相匹配。如果有匹配,main()将该行(后跟新行)写入目标文件。(我们将在未来的Java 101教程中探索文件I / O逻辑。)
编译清单1如下:
javac ExtCmnt.java
运行运用程序ExtCmnt.java:
java ExtCmnt ExtCmnt.java out
您该当在out文件中不雅观察以下输出:
// The following pattern defines multiline comments that appear on the
// same line (e.g., / same line /) and single-line comments (e.g., //
// some line). The comment may appear anywhere on the line.
p = Pattern.compile(\"大众./\\.\\/|.//.$\"大众);
if (m.matches()) / entire line must match /
在\"大众./\\.\\/|.//.$\公众模式字符串中,垂直条元字符(|)作为逻辑“或”运算符,见告匹配器利用该运算符的左正则表达式布局操作数来定位匹配器文本中的匹配。如果不存在匹配,则匹配器将在另一匹配考试测验中利用该运算符的正则表达式布局操作数。(捕获组中的括号中的元字符形成另一个逻辑运算符。)
用于词法剖析的正则表达式
正则表达式更有用的运用是一个可重用的库,用于实行词法剖析,这是任何代码编译器或汇编器的关键组件。在这种情形下,输入的字符流被分组成令牌的输出流,其是表示具有集体含义的字符序列的名称。例如,在碰着字母序列c,o,u,n,t,e,r在输入流,词法剖析器可能输出令牌ID(标识符)。与令牌干系联的字符序列称为词法。
更多关于令牌和词汇
一个令牌,如ID表示许多字符序列。对付这样的令牌,令牌表示的实际词汇也是依赖于词法剖析的编译器,汇编器或其他工具所须要的。对付表示单个字符序列的令牌,例如PLUS仅表示+字符,实际的词法不须要,由于它是由令牌所暗示的。
正则表达式比基于状态的词法剖析器更有效率,这些剖析器必须用手编写,常日不可重用。基于正则表达式的词法剖析器的一个例子是JLex,它是Java的词法天生器,它依赖于正则表达式来指定将输入流分解成令牌的规则。另一个例子是Lexan。
理解Lexan
Lexan是用于词法剖析的可重用Java库。它是基于代码中的我思学习网站的利用Java措辞编写一个解析器博客系列。该图书馆由以下课程组成,您可以在ca.javajeff.lexan本文的源代码下载包中找到这些课程:
lLexan:词汇剖析器
lLexanException:由Lexan布局函数引起的非常
lLexException:在词法剖析期间由于语法缺点引起的非常
lToken:具有正则表达式属性的名称
lTokLex:一个令牌/ lexeme对
该Lexan(java.lang.Class<?> tokensClass)布局函数创建一个新的词法剖析器。它须要一个单一的java.lang.Class工具参数来表示一类static Token常量。利用Reflection API,布局函数将每个Token常量读入Token[]值数组。如果没有Token常数存在,LexanException则抛出。
Lexan 还供应了以下一对方法:
lList<TokLex> getTokLexes()返回这个词汇剖析器的TokLexes 列表。
lvoid lex(String str)将输入字符串列入TokLexes 列表。LexException如果碰着不符合任何Token[]数组模式的字符,则会抛出此非常。
LexanException没有供应任何方法,但依赖于其继续getMessage()方法来返回非常的。相反,LexException还供应了以下方法:
lint getBadCharIndex() 返回与任何令牌模式不匹配的字符的索引。
lString getText() 返回发生非常时被词汇的文本。
Token覆盖toString()返回令牌名称的方法。它还供应了String getPattern()一种返回令牌的正则表达式属性的方法。
TokLex供应一种Token getToken()返回其令牌的方法。它还供应一种String getLexeme()返回其词法的方法。
示范Lexan
我创建了一个LexanDemo演示库的运用程序。该运用程序包括LexanDemo,BinTokens,MathTokens,和NoTokens类。清单2显示了LexanDemo源代码。
清单2.演示Lexan
import ca.javajeff.lexan.Lexan;
import ca.javajeff.lexan.LexanException;
import ca.javajeff.lexan.LexException;
import ca.javajeff.lexan.TokLex;
public final class LexanDemo
{
public static void main(String[] args)
{
lex(MathTokens.class, \"大众 sin(x) (1 + var_12) \"大众);
lex(BinTokens.class, \"大众 1 0 1 0 1\"大众);
lex(BinTokens.class, \"大众110\"大众);
lex(BinTokens.class, \"大众1 20\"大众);
lex(NoTokens.class, \"大众\"大众);
}
private static void lex(Class<?> tokensClass, String text)
{
try
{
Lexan lexan = new Lexan(tokensClass);
lexan.lex(text);
for (TokLex tokLex: lexan.getTokLexes())
System.out.printf(\公众%s: %s%n\"大众, tokLex.getToken(),
tokLex.getLexeme());
}
catch (LexanException le)
{
System.err.println(le.getMessage());
}
catch (LexException le)
{
System.err.println(le.getText());
for (int i = 0; i < le.getBadCharIndex(); i++)
System.err.print(\"大众-\"大众);
System.err.println(\公众^\公众);
System.err.println(le.getMessage());
}
System.out.println();
}
}
清单2的main()方法调用lex()实用程序方法来通过Lexan来演示词法剖析。对这个方法的每个调用都会通报一个Class用于一类令牌和一个字符串进行剖析的工具。
该lex()方法首先实例化Lexan类,将Class工具通报给Lexan布局函数。然后它调用字符串Lexan的lex()方法。
如果词法剖析成功,Lexan的getTokLexes()方法被调用返回的列表TokLex工具。对付每个工具,TokLex的getToken()方法被调用,返回令牌及其getLexeme()方法被调用返回语义。输出两个值。如果词法剖析失落败,LexanException或者LexException被抛出并妥善处理。
为简洁起见,我们仅考虑MathTokens组成本运用程序的别的课程。清单3显示了这个类的源代码。
清单3.描述一组小数学措辞的令牌
import ca.javajeff.lexan.Token;
public final class MathTokens
{
public final static Token FUNC = new Token(\"大众FUNC\"大众, \"大众sin|cos|exp|ln|sqrt\公众);
public final static Token LPAREN = new Token(\公众LPAREN\"大众, \"大众\\(\"大众);
public final static Token RPAREN = new Token(\"大众RPAREN\公众, \"大众\\)\"大众);
public final static Token PLUSMIN = new Token(\"大众PLUSMIN\公众, \公众[+-]\公众);
public final static Token TIMESDIV = new Token(\"大众TIMESDIV\"大众, \公众[/]\"大众);
public final static Token CARET = new Token(\"大众CARET\"大众, \"大众\\^\"大众);
public final static Token INTEGER = new Token(\公众INTEGER\"大众, \"大众[0-9]+\"大众);
public final static Token ID = new Token(\"大众ID\"大众, \公众[a-zA-Z][a-zA-Z0-9_]\公众);
}
清单3显示MathTokens了一个Token常量序列。每个常量被初始化为一个Token工具。该工具的布局函数吸收一个命名标记的字符串,以及描述属于该标记的所有字符串的正则表达式。基于字符串的令牌名称该当与常量的名称相匹配(为了清楚起见),但这不是逼迫性的。
s Token列表中Token的常量的位置很主要。Token列表中更高的常量优先于较低的常量。例如,sin碰着时,Lexan选择FUNC而不是ID令牌。如果ID涌现之前FUNC,ID将当选中。
编译并运行LexanDemo
本文的源代码下载包含lexan.zip归档,个中包含Lexan的所有发行文件。解压缩此归档,并将当前目录设置为主lexan目录的demos子目录。
如果您利用Windows,请实行以下命令来编译演示的源文件:
javac -cp ..\library\lexan.jar .java
编译成功后,实行此命令运行演示:
java -cp ..\library\lexan.jar;. LexanDemo
您该当把稳以下输出:
FUNC: sin
LPAREN: (
ID: x
RPAREN: )
TIMESDIV:
LPAREN: (
INTEGER: 1
PLUSMIN: +
ID: var_12
RPAREN: )
ONE: 1
ZERO: 0
ONE: 1
ZERO: 0
ONE: 1
ONE: 1
ONE: 1
ZERO: 0
1 20
--^
Unexpected character in input: 20
no tokens
该Unexpected character in input: 20源于抛出的LexanException,这是由于BinTokens不定义一个Token常量2作为其正则表达式引起的。请把稳非常处理程序输出的笔墨被引用和令人反感的字符的位置。所述no tokens源于抛出LexException由于NoTokens没有定义Token的常数。
幕后
Lexan依赖Lexan该类作为引擎。查看清单4,看看这个类是如何实现的,以及正则表达式如何有助于引擎的可重用性。
清单4.构建基于正则表达式的词法剖析器
package ca.javajeff.lexan;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
/
A lexical analyzer. You can use this class to transform an input stream of
characters into an output stream of tokens.
@author Jeff Friesen
/
public final class Lexan
{
private List<TokLex> tokLexes;
private Token[] values;
/
Initialize a lexical analyzer to a set of Token objects.
@param tokensClass the Class object of a class containing a set of Token
objects
@throws LexanException unable to construct a Lexan object, possibly
because there are no Token objects in the class
/
public Lexan(Class<?> tokensClass) throws LexanException
{
try
{
tokLexes = new ArrayList<>();
List<Token> _values = new ArrayList<>();
Field[] fields = tokensClass.getDeclaredFields();
for (Field field: fields)
if (field.getType().getName().equals(\"大众ca.javajeff.lexan.Token\"大众))
_values.add((Token) field.get(null));
values = _values.toArray(new Token[0]);
if (values.length == 0)
throw new LexanException(\公众no tokens\"大众);
}
catch (IllegalAccessException iae)
{
throw new LexanException(iae.getMessage());
}
/
Get this lexical analyzer's list of toklexes.
@return list of toklexes
/
public List<TokLex> getTokLexes()
{
return tokLexes;
}
/
Lex an input string into a list of toklexes.
@param str the string being lexed
@throws LexException unexpected character found in input
/
public void lex(String str) throws LexException
{
String s = new String(str).trim(); // remove leading whitespace
int index = (str.length() - s.length());
tokLexes.clear();
while (!s.equals(\"大众\公众))
{
boolean match = false;
for (int i = 0; i < values.length; i++)
{
Token token = values[i];
Matcher m = token.getPattern().matcher(s);
if (m.find())
{
match = true;
tokLexes.add(new TokLex(token, m.group().trim()));
String t = s;
s = m.replaceFirst(\"大众\公众).trim(); // remove leading whitespace
index += (t.length() - s.length());
break;
}
}
if (!match)
throw new LexException(\"大众Unexpected character in input: \"大众 + s, str,
index);
}
}
}
该lex()方法中的代码基于Cogito学习博客“ 写一个Java中的解析器:令牌 ”中供应的代码。查看该帖子,理解Lexan如何利用Regex API进行代码编译。
结论是
正则表达式是每个开拓职员须要理解的有用工具。Java的Regex API可以轻松将其集成到运用程序和库中。现在,您已经对正则表达式和这个API有了基本的理解,学习java.util.regexSDK文档可以进一步理解正则表达式和其他API方法。