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

Java 入门教程

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

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

JavaJava 入门教程正则表达式PatternMatcher

64. 正则表达式

本节目标:学会用一串规则描述”什么样的字符串算合法”,并在 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
```java

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

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

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

```java
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
```bash

## 量词:重复几次

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

- `*`:0 次或多次
- `+`:1 次或多次
- `?`:0 次或 1
- `{n}`:恰好 n 次
- `{n,m}`:n 到 m 次
- `{n,}`:至少 n 次

```java
"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
```bash

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

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

## 分组提取:Matcher.group

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

```java
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
}
```bash

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

## 贪婪 vs 非贪婪

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

```java
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
```bash

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

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

## 搜索、替换与模板引擎

正则还常用在这几处:

```java
// 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!
```bash

## 安全细节

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

```java
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();
}
```bash

`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()` 返回 Stream | Java 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 不一样,别踩坑。