本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。
你好,我是看山。
正则表达式是Java变成中一把利器,常出现在文本检查、替换等逻辑中。本文中,我们将深入探讨 Java 正则表达式 API,并研究如何在 Java 编程语言中运用正则表达式。
在正则表达式的领域中,存在多种不同的“风格”可供选择,例如 grep、Perl、Python、PHP、awk 等等。这就意味着,在一种编程语言中有效的正则表达式,在另一种语言中可能无法正常工作。Java 中的正则表达式语法与 Perl 中的最为相似。
一、简介
在 Java 中使用正则表达式无需进行特殊的设置,JDK 包含了一个专门用于正则操作的包java.util.regex
,我们只需在代码中导入该包即可。此外,java.lang.String
类也内置了对正则表达式的支持,这在日常编码中经常会用到。
java.util.regex
包主要由三个类组成:Pattern
、Matcher
和PatternSyntaxException
。
Pattern
是经过编译的正则表达式。Pattern
类没有提供公共的构造函数,要创建一个Pattern
对象,必须先调用其公共的静态compile
方法,该方法会返回一个Pattern
对象,其第一个参数即为要编译的正则表达式。Matcher
用于解释模式,并针对输入字符串执行匹配操作。它同样没有公共构造函数,我们通过在Pattern
对象上调用matcher
方法,并传入要检查匹配的文本,从而获得一个Matcher
对象。PatternSyntaxException
是一个非受检异常,用于指示正则表达式模式中的语法错误。
我们将详细探究这些类,但在此之前,我们需要先了解如何在 Java 中构建正则表达式。如果我们已经熟悉其他环境中的正则表达式,可能会发现一些细微的差异,但总体来说区别不大。
二、简单示例
让我们从正则表达式的一个最简单用例开始。当将正则表达式应用于字符串时,它可能匹配零次或多次,java.util.regex
API支持的最基本的模式匹配形式是字符串字面量的匹配。
例如,如果正则表达式为“foo”,输入字符串也是“foo”,那么匹配将成功,因为这两个字符串完全相同:
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foo");
assertTrue(matcher.find());
我们首先通过调用Pattern
类的compile
方法,并传入所需的正则表达式创建一个Pattern
对象。然后,通过调用matcher
方法,并传入要检查匹配的文本,创建一个 Matcher 对象。最后,调用find
方法。find
方法会在输入文本中不断前进,每次找到匹配项时返回true
,因此我们也可以用它来计算匹配的次数:
Pattern pattern = Pattern.compile("foo");
Matcher matcher = pattern.matcher("foofoo");
int matches = 0;
while (matcher.find()) {
matches++;
}
assertEquals(matches, 2);
后续我们会重复使用这个逻辑,可以将查找匹配次数的逻辑抽象到一个名为runTest
的方法:
public static int runTest(String regex, String text) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
int matches = 0;
while (matcher.find()) {
matches++;
}
return matches;
}
当匹配次数为0
时,表示待匹配文本中没有命中正则表达式。
二、元字符
元字符会影响模式的匹配方式,从某种程度上说,它们为搜索模式增添了逻辑。Java API 支持多种元字符,其中最直接的是点“.”,它可以匹配任何字符:
int matches = runTest(".", "foo");
assertTrue(matches > 0);
考虑前面的例子,正则表达式“foo”可以匹配文本“foo”以及“foofoo”两次。如果在正则表达式中使用点元字符,在第二种情况下就不会得到两次匹配:
int matches = runTest("foo.", "foofoo");
assertEquals(matches, 1);
注意正则表达式中“foo”后面的点。匹配器会匹配所有前面是“foo”的文本,因为最后的点表示后面可以是任何字符。所以在找到第一个“foo”后,其余部分被视为任意字符,这就是为什么只有一次匹配。
API 还支持其他一些元字符,如<([{\^-=$!|]})?*+.
,我们将在本文中进一步探讨它们。
三、字符类
在字符类下,大约有六种构造。
(一)或类
我们通过“[abc]”来构造或类,它可以匹配集合中的任意一个元素:
int matches = runTest("[abc]", "b");
assertEquals(matches, 1);
如果它们都出现在文本中,它会分别匹配每个元素,而不考虑顺序:
int matches = runTest("[abc]", "cab");
assertEquals(matches, 3);
它们也可以作为字符串的一部分交替出现。在下面的例子中,当我们通过将集合中的每个元素与第一个字母交替来创建不同的单词时,它们都会被匹配:
int matches = runTest("[bcr]at", "bat cat rat");
assertEquals(matches, 3);
(二)非或类
通过在集合的第一个元素前添加脱字符号“^”,可以否定上述集合:
int matches = runTest("[^abc]", "g");
assertTrue(matches > 0);
再看另一个例子:
int matches = runTest("[^bcr]at", "sat mat eat");
assertTrue(matches > 0);
(三)范围类
我们可以使用连字符“-”定义一个类,指定匹配文本应所属的范围。同样,我们也可以否定一个范围。
匹配大写字母:
int matches = runTest("[A-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
匹配小写字母:
int matches = runTest("[a-z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 26);
匹配大写和小写字母:
int matches = runTest("[a-zA-Z]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 28);
匹配给定范围的数字:
int matches = runTest("[1-5]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 2);
匹配另一个范围的数字:
int matches = runTest("3[0-5]", "Two Uppercase alphabets 34 overall");
assertEquals(matches, 1);
(四)并集类
并集字符类是由两个或多个字符类组合而成的:
int matches = runTest("[1-3[7-9]]", "123456789");
assertEquals(matches, 6);
上述测试只会匹配九个整数中的六个,因为并集跳过了 4、5 和 6。
(五)交集类
与并集类类似,交集类是从两个或多个集合中选取共同元素得到的。要应用交集,我们使用“&&”:
int matches = runTest("[1-6&&[3-9]]", "123456789");
assertEquals(matches, 4);
我们会得到四个匹配,因为两个集合的交集只有四个元素。
(六)差集类
我们可以使用减法来否定一个或多个字符类。例如,我们可以匹配一组奇数:
int matches = runTest("[0-9&&[^2468]]", "123456789");
assertEquals(matches, 5);
只有 1、3、5、7、9 会被匹配。
文末总结
正则表达式是一个开发利器,用的好的话,会大大提升我们的开发效率。本文介绍了元字符和关系运算,后续慢慢展开,把正则表达式的各种功能展示出来。
文中代码在公众号「看山的小屋」,回复“java”可获得。我的个人微信号是:hellokanshan ,如果有需要可以添加。
青山不改,绿水长流,我们下次见。
推荐阅读
- 从小工到专家的 Java 进阶之旅
- 并发编程
- MapStruct实用手册
- 一个架构师的素养修炼
- 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
- 一文掌握 Java8 的 Optional 的 6 种操作
- 使用 Lambda 表达式实现超强的排序功能
- Java11 中基于嵌套关系的访问控制优化
- 原来OpenFeign功能这么强大
- 原来OpenFeign功能这么强大(第二弹)
- 使用OpenFeign的5个步骤和7个高级功能
- 由浅入深掌握CompletableFuture的七种用法
- 全新的HttpClient,现代高效的网络通信利器
- Java 中反射、内省的性能差距居然如此
- 比反射更好用的内省
- 异常处理的9条建议
- Java原生支持Lombok了
- MapStruct教程
- 手写重试器
- 等待线程完成的三种常用方式
- 如何在线程完成时执行回调
- 由浅入深掌握Future
- 由浅入深掌握ExecutorService
- Fork/Join框架快速上手指南
- 如何延迟执行一段代码
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
- 个人主页:https://www.howardliu.cn
- 个人博文:正则表达式实用指南(一):元字符及关系运算
- CSDN 主页:https://kanshan.blog.csdn.net/
- CSDN 博文:正则表达式实用指南(一):元字符及关系运算