首页 / Java 入门教程 / 正则表达式:用 Pattern 与 Matcher 匹配文本

Java 入门教程

正则表达式:用 Pattern 与 Matcher 匹配文本

本教程共 100 篇 · 第 64 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程正则表达式PatternMatcher

本节目标:学会用一串规则描述”什么样的字符串算合法”,并在 Java 里用 Pattern 和 Matcher 做匹配、提取、搜索和替换。

判断手机号、邮箱、邮编是否合法,如果全靠 if 一个个字符去比,代码又臭又长。正则表达式(regex)就是用”一串规则文字”来描述模式,再交给引擎去匹配,一行顶你几十行。

在 Java 里写正则要注意转义

正则本身就是字符串,而 \ 在 Java 字符串里也是转义符。所以正则里的 \d,写进 Java 要变成 "\\d"

String regex = "20\\d\\d";   // 正则含义:20 开头,后面两个数字
System.out.println("2019".matches(regex)); // true
System.out.println("2100".matches(regex)); // false

String.matches(regex) 是最简单的入口,判断整个字符串是否完全匹配该规则。

Warning

正则里的 \ 在 Java 字符串里要写成 \\。匹配一个反斜杠本身,正则写成 \\,Java 字符串得写 "\\\\"。这是新手最常栽的坑。

Java 15 引入了文本块(Text Block),写正则更清爽,不用双重转义了(但正则内部的 \ 还是要转):

String regex = """
    \\d{3,4}-\\d{7,8}    # 电话号码:3-4位区号-7-8位号码
    """;

文本块用 """ 包裹,内部可以换行加注释,比一行字符串直观多了。

单个字符的匹配规则

写法含义能匹配
A指定字符A
.任意字符a&0
\d数字 0~90~9
\w字母数字下划线a9_
\s空格或 Tab空格、\t
\D \W \S上面的反义非数字、非\w、非空白
"java9".matches("java\\d");  // true
"java#".matches("java\\w");  // false

量词:重复几次

光匹配一个字符不够,量词控制”重复多少遍”:

  • *:0 次或多次
  • +:1 次或多次
  • ?:0 次或 1 次
  • {n}:恰好 n 次
  • {n,m}:n 到 m 次
  • {n,}:至少 n 次
"A380".matches("A\\d+");     // true,\d+ 匹配 380
"010-12345678".matches("\\d{3,4}-\\d{7,8}"); // true
Note

量词默认是”贪婪”的,会尽量多匹配。想变”非贪婪”,在量词后加 ?,比如 *?+???

复杂规则:开头结尾、范围、或

  • ^ 表示字符串开头,$ 表示结尾,常用在整串校验。
  • [...] 表示字符范围:[1-9][a-fA-F][^0-9] 表示”非数字”。
  • | 表示”或”:java|php|go
"^[1-9]\\d{5,6}$".matches("123456"); // 思路:6~7位、不以0开头
"java|php".matches("php"); // true

用括号 (...) 可以把公共部分提取出来:learn\s(java|php|go)

边界匹配还有 \b(单词边界)和 \B(非单词边界),做”匹配整个单词而非子串”时很有用,比如 \bcat\b 不会匹配 category 里的 cat。

分组提取:Matcher.group

光判断还不够,常要从字符串里抠出子串。String.matches 做不到,得上 PatternMatcher

import java.util.regex.*;

Pattern p = Pattern.compile("(\\d{3,4})-(\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
    System.out.println(m.group(0)); // 010-12345678,整体
    System.out.println(m.group(1)); // 010,第1个括号
    System.out.println(m.group(2)); // 12345678,第2个括号
}

group(0) 是整个匹配,group(1)group(2) 对应第几个 (...)。要先 matches() 成功才能取。

Tip

同一个正则要反复用,先 Pattern.compile 一次再反复 matcher,比每次 String.matches 高效。编译一次,多次匹配。

命名分组(Java 7+)

Java 7 开始支持命名分组,比数字更直观:

Pattern p = Pattern.compile("(?<area>\\d{3,4})-(?<number>\\d{7,8})");
Matcher m = p.matcher("010-12345678");
if (m.matches()) {
    System.out.println(m.group("area"));   // 010
    System.out.println(m.group("number")); // 12345678
}

(?<name>...) 给分组起名,用 m.group("name") 取值,比记数字更不容易出错。

贪婪 vs 非贪婪

正则默认”贪婪”,能多匹配就多匹配。看 (<br>)(\d+)(0*)"123000"\d+ 会把后面 0 也吞掉,0* 拿到空。想在 \d+ 后加 ? 变非贪婪:

Pattern p = Pattern.compile("(\\d+?)(0*)");
Matcher m = p.matcher("123000");
m.matches();
System.out.println(m.group(1)); // 123
System.out.println(m.group(2)); // 000

? 紧跟量词后面,意思是”尽量少匹配”。

Warning

贪婪/非贪婪是正则里最容易绕晕的概念。记住口诀:量词后加 ?,“见好就收”;不加 ?,“能吞就吞”。

编译标志:忽略大小写等

Pattern.compile 第二个参数能传标志,改变匹配行为。最常用的 CASE_INSENSITIVE 忽略大小写,MULTILINE^/$ 匹配每行,DOTALL. 也能匹配换行:

Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
System.out.println(p.matcher("HELLO").matches()); // true

多个标志用 | 组合:Pattern.CASE_INSENSITIVE | Pattern.MULTILINE

Note

Java 21 引入了 Pattern.COMMENTS 的增强版,支持在正则里加注释和空白,大幅提升复杂规则的可读性。

搜索、替换与模板引擎

正则还常用在这几处:

// 1. 分割
"a, b ;; c".split("[,\\;\\s]+"); // ["a","b","c"]

// 2. 搜索
Pattern p = Pattern.compile("\\wo\\w");
Matcher m = p.matcher("the brown fox");
while (m.find()) {
    System.out.println(m.group()); // 输出含 o 的三字母词
}

// 3. 替换
"a b  c".replaceAll("\\s+", " "); // "a b c"

循环里 find() 会顺着文本往下找下一个匹配,start()/end() 还能拿到匹配的位置。想做复杂替换(比如模板引擎填充 ${name}),用 appendReplacement + appendTail

Pattern p = Pattern.compile("\\$\\{(\\w+)\\}");
Matcher m = p.matcher("Hello, ${name}!");
StringBuffer sb = new StringBuffer();
while (m.find()) {
    m.appendReplacement(sb, "Bob");
}
m.appendTail(sb);
System.out.println(sb); // Hello, Bob!

安全细节

用户输入当正则时要小心:特殊字符会破坏规则。Pattern.quote 把一段文字当字面量处理,Matcher.quoteReplacement 在替换串里转义 $\

Pattern.quote("a.b.c"); // 把点当成普通的点,不当"任意字符"
Warning

正则写错会抛 PatternSyntaxException(运行时异常)。复杂规则建议先用在线正则测试工具验证,再贴进代码,省去反复试错的麻烦。

正则表达式性能注意

不要在循环里重复 Pattern.compile

// 错的示范:每次循环都编译一次
for (String s : list) {
    Pattern p = Pattern.compile("\\d+");   // 重复编译,性能差
    p.matcher(s).matches();
}

// 正确做法:提到循环外面
Pattern p = Pattern.compile("\\d+");       // 编译一次
for (String s : list) {
    p.matcher(s).matches();
}

Pattern.compile 有开销,编译一次反复用。在需要高并发的场景,可以把 Pattern 声明为 static final 常量,整个应用只编译一次。

Java 25 与老写法对照

正则相关 API 在 Java 25 变化不大,但新版本引入了一些便利写法:

写法版本状态
Pattern.compile(regex)Java 1.4+✅ 标准
Pattern.compile(regex, flags)Java 1.4+✅ 带标志
(?<name>...) 命名分组Java 7+✅ 推荐
文本块写正则 """..."""Java 15+✅ 推荐
Matcher.results() 返回 StreamJava 9+✅ 函数式
Matcher.replaceAll(Function)Java 9+✅ 灵活替换

老代码里常见的 new Pattern() 是不存在的——Pattern 没有公开构造方法,只能用 Pattern.compile 获取实例。

速查表

语法含义
.任意字符
\d \D数字 / 非数字
\w \W词字符 / 非词字符
\s \S空白 / 非空白
* + ?0或多 / 1或多 / 0或1
{n} {n,m} {n,}恰好n / n到m / 至少n
^ $开头 / 结尾
[...] [^...]字符范围 / 范围取反
``
(...)分组
(?<name>...)命名分组(Java 7+)
*? +? ??非贪婪量词
\b \B单词边界 / 非单词边界
方法用途
String.matches(regex)整串匹配
Pattern.compile(regex)编译正则
Matcher.matches()整串匹配
Matcher.find()搜索下一个匹配
Matcher.group()取匹配内容
Matcher.start() end()取匹配位置
String.split(regex)分割
String.replaceAll(regex, repl)替换全部
Pattern.quote(s)转义成字面量

常见疑问

Q:Java 正则和其他语言的正则一样吗? 基本语法大同小异,但实现细节有差别。Java 用 Pattern/Matcher,转义用 \\,没有 /regex/ 的写法。正则不是标准,但核心语法(量词、分组、字符类)各语言通用。

Q:matches()find() 有什么区别? matches() 要求整个字符串完全匹配规则。find() 只要求在字符串中找到一处匹配就行。校验手机号用 matches,搜索文本里的关键词用 find

Q:为什么 replaceAll$\ 要转义? 替换串里的 $1 表示引用第一组,\ 是转义符。普通字符串想表示字面的 $\,要用 Matcher.quoteReplacement 包一层,避免被当成特殊符号解析。

Q:正则表达式怎么调试? 推荐两个思路:一是在线工具(如 regex101.com)先验证规则,选好 Java 方言再贴进代码;二是 Java 里用 Pattern.matcher(s).results().count() 快速验证匹配数量。

Q:如何匹配中文字符?\p{Script=Han} 匹配汉字(Java 7+),或者用 [\u4e00-\u9fa5] 匹配基本汉字。\w 不匹配中文,这点和 Perl/Python 不一样,别踩坑。