Java 正则表达式实战指南:从基础语法到进阶技巧
Java 正则表达式实战指南:从基础语法到进阶技巧
在日常开发中,字符串处理是高频需求,而正则表达式(Regular Expression,简称 Regex)就是处理文本的利器。Java 提供了功能强大的 Pattern 和 Matcher 类来支持正则表达式操作,可以帮助我们完成 匹配、查找、替换、分割 等任务。
本文将从 基础语法 开始,逐步带你掌握 Java 中的正则表达式,并通过实例展示实际应用场景。
一、正则表达式基础语法
正则表达式语法规则较多,不急于一时全都记住,可以大概有个印象,用到时来找对应的具体语法;通过几个具体案例实践练习一下,可以帮助我们更快的记忆这些规则。
1. 字符类
[abc] 只能是 a 或 b 或 c
[^abc] 除了 a, b, c 之外的任何字符
[a-zA-Z] a 到 z 或 A 到 Z
[a-d[A-Z]] a 到 d 或 A 到 Z
[a-z&&[def]] a-z 与 def 的交集 → d, e, f
[a-z&&[^bc]] a-z 中去掉 b, c
[a-z&&[^m-p]] a-z 中去掉 m 到 p
要点快记:
- 方括号[ ] 里的元素只能出现一次;
- ^X :表示除了X之外的其他元素;
- && :表示 ‘和’ 的意思,两边条件需要同时满足。
2. 预定义字符
. 任意字符
\d 数字 [0-9]
\D 非数字
\s 空白字符
\S 非空白字符
\w 单词字符 [a-zA-Z_0-9]
\W 非单词字符
⚠️ 在 Java 字符串中常见 \\d,因为在代码中 \ 本身需要转义。
3. 数量词
X? 出现 0 或 1 次
X* 出现 0 次或多次
X+ 出现 1 次或多次
X{n} 出现 n 次
X{n,} 至少出现 n 次
X{n,m} 出现 n 到 m 次
要点快记:
- { }用来自定义次数;
- X?表示 “0或1次”(联想辅助记忆:?疑问有没有出现,有/无 —> 1/0)
- X+ 和 X*(联想辅助: + 为正,正数不包括0,即X+表示 “1次或多次”;X* 则表示 “0次或多次” )。
4. 常用正则工具推荐
实际情况中,可以使用插件找到类似的正则表达式进行修改,如any_rule插件,里面包含各类常用的regex,如身份证号,手机号码等,以提高代码效率。

二、Pattern 与 Matcher 的使用
知识点
Java 提供了以下两个核心类:
- Pattern:正则表达式的编译表示
- Matcher:文本匹配器,用来在字符串中查找符合规则的子串
我们以一个实例了解上述方法在具体场景的用法。
案例
示例:提取一段文字中的 Java 版本号。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ZhengZhe {
public static void main(String[] args) {
//从一段文字中提取想要的东西: Java 版本号
String str = "Java自从95年问世依赖,经历了很多版本,目前企业中常用的 是Java8和Java11,"+
"因为这两个是长期支持版本,下一个长期支持版本的是Java17,相信在未来不久Java17也会逐渐登上历史的舞台";
//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//2. 获取文本匹配器的对象
Matcher m = p.matcher(str);
//拿着m去读str,找符合p规则的子串
Method(str);
//这个方法内部可以帮助理解 m.find() 与 m.group() 工作原理
System.out.println("--------------------");
//3.利用循环获取
while(m.find()){
String s = m.group();
System.out.println(s); //Java Java8 Java11 Java17 Java17
}
}
public static void Method(String str){
/*
m.find() 与 m.group() 工作原理:
*/
Pattern p = Pattern.compile("Java\\d{0,2}");
Matcher m = p.matcher(str);
boolean b = m.find();
//原理:拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
// 无,返回false; 有,返回true,在底层记录了子串的起始索引和结束索引+1
String s1 = m.group();
System.out.println(s1); // Java
//原理:方法底层根据find方法记录的索引进行字符串的截取:subString(起始索引,结束索引),包头不包尾;如,(0,4),不包含4索引
//会把截取的小串进行返回
b = m.find();
//第二次调用find,方法会继续往后读,直到找到第二个子串返回true/没找到返回false
String s2 = m.group();
System.out.println(s2); // Java8
}
}
输出:
Java
Java8
-------------------------
Java
Java8
Java11
Java17
Java17
三、进阶技巧与应用案例
本章节通过实际案例介绍以下文本处理方法:零宽断言、贪婪/非贪婪匹配、字符串替换(replaceAll)与分割(split)、捕获分组与非捕获分组。
1. 零宽断言 & 贪婪爬取与非贪婪爬取
知识点讲解
(1) 零宽断言(Lookaround)
零宽断言分为 4 种:
-
正向前瞻 (?=X):匹配后面紧跟着 X 的位置
示例:\d+(?=元) → 匹配数字,要求数字后面必须是“元” -
负向前瞻 (?!X):匹配后面不是 X 的位置
示例:\d+(?!元) → 匹配数字,要求数字后面不能是“元” -
正向后顾 (?<=X):匹配前面是 X 的位置
示例:(?<=@)\w+ → 匹配 @ 后面的单词字符 -
负向后顾 (?<!X):匹配前面不是 X 的位置
示例:(?<!@)\w+ → 匹配前面不是 @ 的单词字符
(2) 贪婪匹配与非贪婪匹配
- 正则中的数量词默认是 贪婪模式,会尽可能多地匹配字符。例如:
".+"
在字符串 “abc def” 上匹配时,会得到整个 “abc def”。
- 如果改为 非贪婪模式(在数量词后加 ?),则会尽可能少地匹配字符:
".+?"
在 “abc def” 上匹配时,结果是 “abc”(遇到第一个空格就停止)。
案例
案例:对指定字符串按照需求进行文本处理。
需求1:提取版本号为8、11、17的Java文本(不显示版本号)
需求2:提取版本号为8、11、17的Java文本(包含版本号)
需求3:提取版本号非8、11、17的其他Java文本
需求4:正则表达式匹配模式:非贪婪匹配文中的ab;贪婪匹配文中中的abbb…
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ZhengZe2 {
public static void main(String[] args) {
/*
对下面一段文字提取指定内容
需求1:爬取版本号为8,11,17的java,但不输出版本号;
需求2:爬取版本号为8,11,17的java,并且输出版本号
需求3:爬取除了版本号为8,11,17的其他Java文本
需求4:贪婪爬取ab+(Java默认是贪婪爬取),非贪婪爬取ab,正则:(ab+?)
*/
String str = "Java自从95年问世依赖,经历了很多版本,目前企业中常用的 是Java8和Java11,abbbbbbbbbbbbbbbbbbaaaaaaa"+
"因为这俩呢个是长期支持版本,下一个长期支持版本的是Java17,相信在未来不久Java17也会逐渐登上历史的舞台";
Pattern p1 = Pattern.compile("((?i)Java)(?=8|11|17)"); //需求1
/*
最前面的(?i)是忽略大小写的意思
?= 的解释(辅助记忆):
?可以看作是指代前面的Java
= 表示在Java后面要跟随的数据,但在获取的时候,只获取前半部分
*/
Pattern p2 = Pattern.compile("((?i)Java)(?:8|11|17)"); //需求2
// Pattern p2 = Pattern.compile("((?i)Java)(8|11|17)"); //需求2
Pattern p3 = Pattern.compile("((?i)Java)(?!8|11|17)"); //需求3
Pattern p4 = Pattern.compile("ab+?");//需求4
Pattern p5 = Pattern.compile("ab+");
Matcher m1 = p1.matcher(str);
while(m1.find()){
String s1 = m1.group();
System.out.println(s1); // Java Java Java Java
}
System.out.println("--------------------");
Matcher m2 = p2.matcher(str);
while(m2.find()){
String s2 = m2.group();
System.out.println(s2); //Java8 Java11 Java17 Java17
}
System.out.println("--------------------");
Matcher m3 = p3.matcher(str);
while(m3.find()){
String s3 = m3.group();
System.out.println(s3);//Java
}
Matcher m4 = p4.matcher(str);
while(m4.find()){
String s4 = m4.group();
System.out.println(s4); //ab
}
Matcher m5 = p5.matcher(str);
while(m5.find()){
String s5 = m5.group();
System.out.println(s5); //abbbbbbbbbbbbbbbbbb
}
}
}
小结:
零宽断言 允许我们定义“必须匹配的上下文条件”,但不把条件本身包含进结果。
贪婪模式 会尽量多匹配,非贪婪模式 会尽量少匹配。
2. 字符串替换与切割
本节介绍两个常用识别正则的方法:replaceAll、split。
public String replaceAll(String regex,String newStr) 按照正则表达式的规则进行替换
public String[] split(String regex) 按照正则表达式规则切割字符串
我们通过案例来学习这两个方法的使用。
要求1:将下面一段字符串中的杂乱字母数字换成vs
要求2:将字符串中的三个姓名分割出来
替换:replaceAll
String s = "喜羊羊dfhlufbkjcsdi666美羊羊kjfhurecbiiad888懒羊羊";
String result = s.replaceAll("[\\w&&[^_]]+", "vs");
System.out.println(result);
//底层代码原理:先创建文本编辑器对象,从头读取字符串中的内容,只要有满足的,就用第二个参数去替换。
输出:
喜羊羊vs美羊羊vs懒羊羊
切割:split
String[] arr = s.split("[\\w&&[^_]]+");
for (String name : arr) {
System.out.println(name);
}
输出:
喜羊羊
美羊羊
懒羊羊
四、分组与引用:捕获分组与非捕获分组
在正则表达式中,分组(Grouping) 能够让我们将部分表达式用 () 括起来,形成独立单元,从而实现 引用、复用或逻辑分块。
分组分为两大类:捕获分组(Capturing Group) 和 非捕获分组(Non-Capturing Group)。
1. 捕获分组
捕获分组的特点是:
-
默认行为:
()会自动捕获内容,并分配一个组号。 -
组号分配规则:从左到右,最左边的
(为第 1 组,依次递增。 -
分组内容复用:
- 在正则内部可用
\\组号引用 - 在正则外部替换时可用
$组号引用
- 在正则内部可用
案例 1:判断字符串首尾字符是否一致
String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1)); // true
System.out.println("2344i".matches(regex1)); // false
解释:
(.)捕获首字符\\1引用第 1 组,即首字符.+表示中间可以有任意字符
👉 只要首尾字符一致,就能匹配成功。
案例 2:判断字符串首尾子串是否一致
String regex2 = "(.+).+\\1";
System.out.println("abc123abc".matches(regex2)); // true
System.out.println("b456b".matches(regex2)); // true
System.out.println("ab123ba".matches(regex2)); // false
解释:
(.+)捕获开头的一段字符串\\1表示结尾部分必须与开头捕获的内容一致
案例 3:开头连续字符 + 结尾连续字符判断
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3)); // true
System.out.println("aba123baa".matches(regex3)); // true
System.out.println("ab123bc".matches(regex3)); // false
解释:
(.)捕获首字符(记为第 2 组)\\2*表示重复该字符多次- 整体
((.)\\2*)捕获连续的相同字符(记为第 1 组) \\1要求结尾部分与开头连续字符相同
案例 4:去除重复字符
String str = "我要学学编编编程程程程。";
String result = str.replaceAll("(.)\\1+","$1");
System.out.println(result); // 我要学编程。
解释:
(.)捕获一个字符\\1+表示该字符至少再重复一次$1表示替换时只保留第一次捕获的字符
👉 实现“连续字符去重”的效果。
2. 非捕获分组
非捕获分组的特点是:
-
语法形式:
(?:pattern)、(?=pattern)、(?!pattern) -
不占用组号,仅用于逻辑分组或条件约束。
-
常见用法:
(?:abc):仅作为整体匹配,不记录分组(?=abc):正向前瞻,匹配后面必须是abc(?!abc):负向前瞻,匹配后面不能是abc
👉 它在前文的“零宽断言”部分已详细介绍。
3. 小结
- 捕获分组(默认):
- 自动分配组号,从 1 开始
- 可在正则内部用
\\n引用,在外部用$n引用 - 常用于判断开头和结尾是否一致、字符串替换等场景
- 非捕获分组:
- 不分配组号,仅作逻辑分组或上下文约束
- 常见形式:
(?:...)、(?=...)、(?!...)
- 应用技巧:
- 判断首尾一致 → 捕获分组 +
\\n引用 - 去除重复字符 → 捕获分组 +
$n替换 - 条件文本提取 → 非捕获分组 + 零宽断言
四、总结
本文系统梳理了 Java 正则表达式的基础语法与常见用法,从字符类、预定义字符、数量词入手,逐步延伸到 Pattern/Matcher 的使用。文章重点讲解了 零宽断言、贪婪与非贪婪匹配、字符串替换与切割、分组与引用 等实用技巧,并辅以案例代码进行演示。通过学习,读者可以掌握 Java 正则的核心方法和应用场景,在实际开发中高效完成文本提取、替换与验证等任务。
正则表达式的威力强大,但也容易写得晦涩。掌握常见模式,并通过实际案例练习,才能真正发挥它在 Java 开发中的价值。
更多推荐



所有评论(0)