Java 正则表达式完全指南:从入门到实战

5 阅读2分钟

Java 正则表达式完全指南:从入门到实战

正则表达式是每个开发者都绕不开的必修课。它强大、实用,却也以"难学难懂"著称。本文将带你系统梳理正则表达式的核心概念,并结合 Java 实战场景,帮助你真正掌握这门技术。


一、什么是正则表达式?

正则表达式(Regular Expression,简称 Regex)是一组用于描述文本规则的模式语言。你可以用它来:

  • 匹配验证:判断字符串是否符合某种格式(如手机号、邮箱、日期)
  • 提取信息:从大段文本中抓取关键字段
  • 文本处理:对符合规则的内容进行替换、分割等操作

要不要学正则表达式? 答案是肯定的。它是程序员的必备工具,能用极少的代码完成复杂的文本处理,也能在支持正则的编辑器(如 VS Code、IntelliJ IDEA)中大幅提升工作效率。

正则为什么难学? 因为它的语法与日常编程思维差异较大,需要耐心拆解、逐步分析,并借助在线工具(如 regex101.com)反复验证。一开始觉得难是完全正常的,坚持实践是唯一的出路。


二、常见正则表达式示例

在深入语法之前,先来感受几个实用的正则表达式:

正则表达式用途说明
^\d{5,12}$5 到 12 位的纯数字(如用户名规则)
`^(0[1-9][0-9]*)$`非负整数(0 或不以 0 开头的正整数)
^(-?\d+)(.\d+)?$整数或小数(可带负号)
`\d{3}-\d{8}\d{4}-\d{7}`国内固定电话(如 010-12345678、021-1234567)
^\d{4}-\d{1,2}-\d{1,2}$日期格式 yyyy-MM-dd
\n\s*\r空白行

三、元字符速查表

元字符是正则表达式的基础"词汇",理解它们是学习正则的第一步。

3.1 位置与通配符

元字符说明
^字符串的开始位置
$字符串的结束位置
.匹配任意单个字符(通常不含换行符)
\w单个"word"字符:字母 / 数字 / 下划线 / 汉字
\s单个空白字符(包含 \n\r\t
\d单个数字字符(等价于 [0-9]
\b单词的开始或结束边界

3.2 重复限定符

符号说明
*0 次或多次
+1 次或多次
?0 次或 1 次(可选)
{n}恰好 n 次
{n,}至少 n 次
{n,m}n 到 m 次

⚠️ 注意:当这些元字符需要表达字面含义时(如匹配一个真实的 * 号),必须使用反斜杠转义,写成 *

3.3 选择与字符集

正则表达式说明
[aeiou]匹配 a、e、i、o、u 中的任意一个字符
[0-9]匹配单个数字字符
[A-Z]匹配单个大写字母
[A-Z0-9_]匹配大写字母、数字或下划线之一
`Hihi`匹配 Hihi(`` 表示"或")

3.4 反义符号(用得较少)

正则表达式说明
[^aeiou]匹配除 a/e/i/o/u 之外的任意字符
[^x]匹配非 x 的任意字符
\W匹配非 \w 字符
\S匹配非空白字符
\D匹配非数字字符
\B非单词边界位置

四、Java 中的转义规则

Java 字符串本身就有一套转义规则(如 \n 表示换行),而正则表达式也有自己的元字符(如 \d 表示数字)。两者叠加,就产生了"双重转义"的问题。

结论:在 Java 代码中写正则时,所有正则里的 `` 都需要写成 \

正则表达式Java 字符串中的写法
\d"\d"
\w"\w"
\d{3}-\d{8}"\d{3}-\d{8}"

Java 中常用的转义字符:

转义字符含义
\n换行符
\r回车符
\t制表符(Tab)
" '双引号 / 单引号
\反斜杠本身
\uXXXXUnicode 字符

五、Java 中使用正则的两种方式

5.1 String 类的便捷方法

String 类内置了对正则的直接支持,适合简单、低频的场景,无需手动创建 Pattern 对象。

方法作用示例
split(regex)按正则分割字符串"a,b;c".split("[,;]")["a","b","c"]
replaceAll(regex, replacement)替换所有匹配的子串"123abc".replaceAll("\d", "")"abc"
replaceFirst(regex, replacement)仅替换第一个匹配的子串"123abc123".replaceFirst("\d+", "0")"0abc123"
matches(regex)验证整个字符串是否完全匹配"13800138000".matches("1[3-9]\d{9}")true

5.2 Pattern + Matcher(推荐用于高频场景)

String 的正则方法每次调用时,JVM 都会在内部将字符串重新编译为 Pattern 对象。在循环或高频调用场景下,这会带来严重的性能损耗。

推荐做法:将 Pattern 预编译为静态常量,复用该对象。

// ❌ 不推荐:每次调用都会重新编译正则
public boolean isValidPhone(String phone) {
    return phone.matches("1[3-9]\d{9}");
}

// ✅ 推荐:全局预编译,仅编译 1 次
private static final Pattern PHONE_PATTERN = Pattern.compile("1[3-9]\d{9}");

public boolean isValidPhone(String phone) {
    return PHONE_PATTERN.matcher(phone).matches();
}

优化原则总结:

  • 低频、一次性场景:直接用 String 的便捷方法,代码更简洁。
  • 高频、循环或工具类场景:必须用 Pattern 预编译,性能提升可达数十倍。
  • 永远不要在循环内调用 String 的正则方法。

六、分组与捕获

分组是正则表达式最强大的功能之一,允许你把关心的部分"抓"出来单独处理。

6.1 捕获分组 ()

用圆括号包裹的子表达式会被捕获,并按左括号出现的顺序从 1 开始编号,可通过 Matcher.group(n) 获取对应内容。

正则:(a)(b)
匹配:ab
group(1) = "a"
group(2) = "b"

嵌套分组时,编号依然只看左括号的顺序:

正则:((a)(b))
匹配:ab
group(1) = "ab"  // 外层括号,编号最靠前
group(2) = "a"
group(3) = "b"

6.2 非捕获分组 (?:)

若只需要括号的分组功能(用于改变匹配优先级或与 | 配合),但不需要捕获内容,应使用 (?:)。它不会分配编号,可以提升正则性能。

正则:(?:姓名|用户名):(.+)
匹配:姓名:张三
group(1) = "张三"   // (?:...) 不占编号

6.3 核心 API 说明

类 / 方法作用
Pattern.compile(regex)预编译正则表达式
pattern.matcher(input)创建 Matcher,关联待匹配的字符串
matcher.find()在字符串中查找下一个匹配项,返回 boolean
matcher.matches()验证整个字符串是否完全匹配
matcher.group(n)获取第 n 个捕获分组的内容(group(0) = 整个匹配串)

⚠️ 必须先调用 find()matches(),才能调用 group(),否则会抛出 IllegalStateException


七、综合实战示例

示例 1:提取中文文本中的字段

String text = "姓名:张三,年龄:25";
Pattern pattern = Pattern.compile("姓名:(.+),年龄:(\d+)");
Matcher matcher = pattern.matcher(text);

if (matcher.matches()) {
    String name = matcher.group(1); // 张三
    String age  = matcher.group(2); // 25
    System.out.println("姓名:" + name + ",年龄:" + age);
}

示例 2:循环提取多个匹配项

String text = "订单号:20240501001,金额:99.99;订单号:20240501002,金额:199.99";

// 预编译,避免重复编译
private static final Pattern ORDER_PATTERN =
    Pattern.compile("订单号:(\d+),金额:(\d+\.\d+)");

public static void main(String[] args) {
    Matcher matcher = ORDER_PATTERN.matcher(text);
    while (matcher.find()) {
        String orderNo = matcher.group(1); // 订单号
        String amount  = matcher.group(2); // 金额
        System.out.println("订单号:" + orderNo + ",金额:" + amount);
    }
}
// 输出:
// 订单号:20240501001,金额:99.99
// 订单号:20240501002,金额:199.99

示例 3:手机号验证工具类(最佳实践)

public class PhoneValidator {
    // 静态常量,全局仅编译一次
    private static final Pattern PHONE_PATTERN = Pattern.compile("1[3-9]\d{9}");

    public static boolean isValid(String phone) {
        if (phone == null) return false;
        return PHONE_PATTERN.matcher(phone).matches();
    }
}

八、总结

知识点核心要点
元字符\d\w\s.^$ 是基础,需熟记
重复限定符*+?{n,m} 控制匹配次数
Java 转义正则中的 `` 在 Java 字符串中需写成 \
String 方法matchesreplaceAllsplit 适合简单场景
Pattern 预编译高频场景必须使用,避免重复编译导致性能问题
捕获分组() 捕获内容,编号按左括号顺序,(?:) 不捕获
find() vs matches()find() 查找子串,matches() 验证完整字符串

正则表达式的掌握没有捷径,关键是多写多练,借助工具验证,遇到复杂表达式时耐心拆解——每一个括号、每一个符号都有它的含义。