正则表达式
概述
正则表达式(Regular Expression,常简写为 regex)用于描述字符串的模式,可用来检查「是否匹配」、查找子串、替换文本等。Java 通过 java.util.regex 包提供支持,核心类为 Pattern(编译正则)和 Matcher(在目标字符串上执行匹配)。掌握正则能大大简化字符串校验、解析和清洗等场景的代码。
前置建议
已掌握 String 与字符串操作,了解字符串不可变性与常用方法。
核心类:Pattern 与 Matcher
基本流程
- 用 Pattern.compile(正则) 将正则字符串编译为 Pattern 对象(可复用,线程安全)。
- 用 pattern.matcher(目标字符串) 得到 Matcher,在其上执行「是否整体匹配」「查找」「替换」等操作。
import java.util.regex.Pattern;
import java.util.regex.Matcher;
String regex = "\\d{3}-\\d{4}"; // 例如 123-4567 这样的格式
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher("电话: 123-4567");
// 判断整个目标串是否与正则完全匹配(从开头到结尾)
boolean fullMatch = matcher.matches(); // false,因为前面有 "电话: "
// 判断是否存在子串匹配
matcher.reset(); // 重置到开头,以便再次使用
boolean found = matcher.find(); // true,找到 "123-4567"提示
在 Java 字符串中,反斜杠 \ 是转义符,所以正则里的 \d 要写成 "\\d",否则 \d 在源码里会被当成非法转义。写正则时心里想的是「一个反斜杠 + 字母」,代码里就要写两个反斜杠。
基本语法速览
| 类别 | 写法 | 含义 |
|---|---|---|
| 字符类 | [abc] | 任一字符 a、b、c |
[a-z] | 小写 a 到 z | |
[^0-9] | 非数字(^ 在 [] 内表示取反) | |
| 预定义 | \d | 数字,等价于 [0-9] |
\w | 单词字符:字母、数字、下划线 | |
\s | 空白(空格、制表、换行等) | |
| 量词 | * | 0 次或多次 |
+ | 1 次或多次 | |
? | 0 次或 1 次 | |
{n} | 恰好 n 次 | |
{n,} | 至少 n 次 | |
{n,m} | n 到 m 次 | |
| 边界 | ^ | 行/串开头(在 [] 外时) |
$ | 行/串结尾 | |
| 分组 | (…) | 捕获组,可用 group(1) 取出 |
以上在 Java 字符串中需对反斜杠转义,例如 \d → "\\d",\\w → "\\\\w"(正则里是 \w)。
基本用法
判断是否整体匹配(校验格式)
适合校验「整串是否符合规则」,如手机号、身份证、邮箱格式。
// 简单示例:是否全为数字
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("12345");
boolean ok = m.matches(); // true
m.reset("12a34");
ok = m.matches(); // false若只校验一次,可直接用 Pattern.matches(regex, input) 静态方法:
boolean valid = Pattern.matches("\\d{11}", "13812345678"); // 11 位数字查找子串(find + group)
需要「在长文本里找出所有符合模式的片段」时,用 find() 循环,再用 group() 取当前匹配到的子串。
String text = "联系张三 13812345678 和李四 13987654321";
Pattern p = Pattern.compile("1[3-9]\\d{9}");
Matcher m = p.matcher(text);
while (m.find()) {
String phone = m.group(); // 当前匹配到的完整子串
System.out.println("找到手机号: " + phone);
}
// 输出: 找到手机号: 13812345678
// 找到手机号: 13987654321分组与捕获
括号 () 形成捕获组,从左到右编号为 1、2、3…,group(0) 表示整个匹配,group(1)、group(2) 为各括号对应的子串。常用于提取「日期中的年/月/日」「标签中的属性」等。
// 从 "2025-02-07" 中提取年、月、日
String line = "日期: 2025-02-07";
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher(line);
if (m.find()) {
String year = m.group(1); // 2025
String month = m.group(2); // 02
String day = m.group(3); // 07
System.out.println(year + "年" + month + "月" + day + "日");
}替换
- replaceAll(replacement):把所有匹配到的子串替换为 replacement。
- replaceFirst(replacement):只替换第一个匹配。
在 replacement 中可用 $1、$2 引用捕获组,$0 表示整段匹配。
String text = "张三 18 岁,李四 20 岁";
Pattern p = Pattern.compile("(\\d+)");
Matcher m = p.matcher(text);
// 给年龄加「岁」后缀(这里年龄本身没带「岁」,仅作替换示例)
String result = m.replaceAll("$1 岁");
// 若原文本是 "张三 18,李四 20",则 result = "张三 18 岁,李四 20 岁"使用示例
示例 1:校验邮箱格式(简化版)
仅作格式校验,不保证该邮箱真实存在。
public static boolean looksLikeEmail(String input) {
if (input == null || input.isBlank()) return false;
// 简化规则:本地部分@域名.后缀
String regex = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$";
return Pattern.matches(regex, input);
}
// 使用
System.out.println(looksLikeEmail("user@example.com")); // true
System.out.println(looksLikeEmail("invalid")); // false示例 2:提取所有数字
在一段文字中找出所有连续数字。
String text = "价格从 100 涨到 200,涨幅 100%。";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(text);
while (m.find()) {
System.out.println(m.group());
}
// 输出: 100 200 100示例 3:隐藏手机号中间 4 位
用分组保留前 3 位和后 4 位,中间用 **** 替换。
String phone = "13812345678";
String masked = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
System.out.println(masked); // 138****5678这里直接用了 String.replaceAll(regex, replacement),底层也是 Pattern/Matcher,适合单次替换。
进阶用法
非捕获组与标志
- (?:...) 表示非捕获组:括号只用于分组或量词,不参与编号,group(1) 不会包含它。
- Pattern.compile(regex, flags) 常用标志:
- Pattern.CASE_INSENSITIVE:忽略大小写;
- Pattern.MULTILINE:
^、$按行匹配(否则仅对整串开头/结尾)。
// 忽略大小写匹配 "java"
Pattern p = Pattern.compile("java", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher("I love JAVA");
System.out.println(m.find()); // true
System.out.println(m.group()); // JAVA贪婪与勉强量词
默认 *、+、?、{n,m} 是贪婪的(尽可能多匹配)。在量词后加 ? 变为勉强(尽可能少匹配),例如 *?、+?。在需要「最小匹配」时使用,可避免匹配过多。
// 从 <p>...</p> 中取内容,若用贪婪会匹配到最后一个 </p>
String html = "<p>first</p> and <p>second</p>";
Pattern greedy = Pattern.compile("<p>.*</p>");
Matcher mg = greedy.matcher(html);
if (mg.find()) {
System.out.println("贪婪: " + mg.group()); // <p>first</p> and <p>second</p>
}
// 勉强:.*? 在遇到第一个 </p> 就停止
Pattern reluctant = Pattern.compile("<p>.*?</p>");
Matcher mr = reluctant.matcher(html);
if (mr.find()) {
System.out.println("勉强: " + mr.group()); // <p>first</p>
}注意事项
注意
Java 字符串中的反斜杠:正则里写 \d,在源码中必须写成 "\\d";正则里写 \\(一个反斜杠),源码中要写 "\\\\"。否则会报错或匹配不符合预期。
注意
matches() 与 find() 的区别:matches() 要求整个目标串从开头到结尾都符合正则;find() 只要存在一段子串匹配即可,且可多次调用依次找下一个匹配。校验「整串格式」用 matches,查找「所有出现」用 find。
注意
灾难性回溯:某些正则(如 (a+)+ 配长串 aaaaaaaaX)在「不匹配」时可能产生大量回溯,导致 CPU 飙高甚至卡死。避免在重复量词上再嵌套重复量词;对用户输入做正则匹配时,可限制输入长度或使用超时/专用库。
提示
复用 Pattern:Pattern 是线程安全的,建议对同一正则只 compile 一次,存成静态或成员变量反复使用;避免在循环里重复 Pattern.compile(...),以提升性能。
相关链接
- String 与字符串操作 — 字符串方法与 replaceAll/replaceFirst
- 常用类(Object、包装类、Math)
- Oracle 官方教程 - 正则表达式
- Pattern (Java SE 17)