Pattern 和 Matcher:正则的两大金刚
学会了语法,该上真刀真枪了。
Java 里处理正则就靠两个类:Pattern 是"模具",Matcher 是"检测员"。模具一次做好能重复用,检测员拿着模具去找匹配。
核心流程
Pattern.compile("正则") → Pattern → .matcher("文本") → Matcher → find()/matches()每一步做什么:
| 步骤 | 代码 | 作用 |
|---|---|---|
| 编译 | Pattern.compile(regex) | 把字符串正则变成 Pattern 对象(只做一次) |
| 创建 | .matcher(text) | 创建一个检测员,拿着模具对准文本 |
| 检测 | find() / matches() | 开始干活 |
Matcher 的三个查找方法
这是最容易混淆的地方。
java
public class FindMethodsCompare {
public static void main(String[] args) {
String input = "123abc";
// matches() - 整个字符串必须完全匹配
System.out.println("matches: " + Pattern.matches("\\d+", input)); // false
System.out.println("matches: " + Pattern.matches("\\d+abc", input)); // true
// find() - 查找子串,找到了就返回 true,可多次调用找下一个
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(input);
System.out.println("find 第1次: " + m.find()); // true
System.out.println("find 第2次: " + m.find()); // false(已找完)
// lookingAt() - 从开头开始匹配,不要求全部匹配
Matcher m2 = Pattern.compile("\\d+").matcher(input);
System.out.println("lookingAt: " + m2.lookingAt()); // true(开头是数字)
String input2 = "abc123";
Matcher m3 = Pattern.compile("\\d+").matcher(input2);
System.out.println("lookingAt: " + m3.lookingAt()); // false(开头不是数字)
}
}一张图记住:
输入: "123abc"
matches() - 要求全部匹配 -> "123abc" 全部是数字?否 -> false
find() - 查找子串 -> "123" 是数字?是 -> true
lookingAt() - 从头匹配 -> 开头 "1" 是数字?是 -> true提取分组内容
从匹配结果中取出捕获组。
java
public class GroupExtractDemo {
public static void main(String[] args) {
String log = "2026-03-22 14:30:00 ERROR [OrderService] 订单失败";
// 分组:年、月、日、时、分、秒、级别、服务名、消息
String pattern = "(?<date>\\d{4}-\\d{2}-\\d{2}) " +
"(?<time>\\d{2}:\\d{2}:\\d{2}) " +
"(?<level>\\w+) " +
"\\[(?<service>\\w+)\\] " +
"(?<message>.+)";
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(log);
if (m.matches()) {
// 方式1:按编号(从 1 开始,0 是整个匹配)
System.out.println("日期: " + m.group(1));
System.out.println("时间: " + m.group(2));
System.out.println("级别: " + m.group(3));
// 方式2:按命名(更清晰,推荐)
System.out.println("日期: " + m.group("date"));
System.out.println("时间: " + m.group("time"));
System.out.println("级别: " + m.group("level"));
System.out.println("服务: " + m.group("service"));
System.out.println("消息: " + m.group("message"));
}
}
}注意:group(0) 是整个匹配,group(1) 开始才是第一个捕获组。
找所有匹配:循环 + find()
find() 的精髓在于可重复调用,每次都从上一次结束的位置继续找。
java
public class FindAllDemo {
public static void main(String[] args) {
String text = "价格: 199元, 原价: 299元, 折扣: 7折";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher(text);
// 循环找所有匹配
System.out.println("找到所有数字:");
while (m.find()) {
// m.group() - 当前匹配的字符串
// m.start() - 在原字符串中的起始位置
// m.end() - 在原字符串中的结束位置(不含)
System.out.printf(" 值: %s, 位置: %d-%d%n",
m.group(), m.start(), m.end());
}
// 输出:
// 值: 199, 位置: 4-7
// 值: 299, 位置: 14-17
// 值: 7, 位置: 24-25
}
}实战:从网页源码提取链接
java
public class ExtractLinksDemo {
public static void main(String[] args) {
String html =
"<a href=\"https://example.com\">示例网站</a>" +
"<a href=\"https://test.org/page\">测试页面</a>";
// href="..." 里面的内容
Pattern linkPattern = Pattern.compile("href=\"([^\"]+)\"");
Matcher m = linkPattern.matcher(html);
while (m.find()) {
// group(0) 是整个匹配 "href=\"...\""
// group(1) 是捕获组,href 属性值
System.out.println("链接: " + m.group(1));
}
// 输出:
// 链接: https://example.com
// 链接: https://test.org/page
}
}替换:replaceAll / replaceFirst
简单替换
java
public class SimpleReplaceDemo {
public static void main(String[] args) {
String text = "hello123world456";
// 替换所有匹配
String result1 = text.replaceAll("\\d+", "#");
System.out.println("全部替换: " + result1); // hello#world#
// 只替换第一个
String result2 = text.replaceFirst("\\d+", "#");
System.out.println("首个替换: " + result2); // hello#world456
}
}反向引用替换
这是最强大的能力:用匹配的内容来构建替换字符串。
java
public class ReplaceWithBackrefDemo {
public static void main(String[] args) {
// 场景1:手机号脱敏 138****5678
String phone = "13812345678";
String masked = phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
System.out.println("手机脱敏: " + masked); // 138****5678
// 场景2:交换 name 和 value
String kv = "name=Alice";
String swapped = kv.replaceAll("(\\w+)=(\\w+)", "$2=$1");
System.out.println("交换: " + swapped); // Alice=name
// 场景3:驼峰转下划线
String camel = "userNameFirst";
String snake = camel.replaceAll("([A-Z])", "_$1").toLowerCase();
System.out.println("驼峰转下划线: " + snake); // user_name_first
// 场景4:日期格式互换
String date = "2026-03-22";
String swappedDate = date.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1");
System.out.println("日期互换: " + swappedDate); // 22/03/2026
}
}函数式替换
Java 9+ 支持 lambda,用代码处理每个匹配。
java
public class LambdaReplaceDemo {
public static void main(String[] args) {
String text = "hello WORLD how are YOU";
// 全部转大写
String upper = Pattern.compile("\\w+")
.matcher(text)
.replaceAll(m -> m.group().toUpperCase());
System.out.println("全大写: " + upper); // HELLO WORLD HOW ARE YOU
// 数字加 1
String math = "1 + 2 = 3";
String incremented = Pattern.compile("\\d+")
.matcher(math)
.replaceAll(m -> String.valueOf(Integer.parseInt(m.group()) + 1));
System.out.println("数字+1: " + incremented); // 2 + 3 = 4
}
}分割:split
java
public class SplitDemo {
public static void main(String[] args) {
// 基本分割
String csv = "apple,banana,cherry";
String[] parts = csv.split(",");
System.out.println("逗号分割: " + Arrays.toString(parts));
// [apple, banana, cherry]
// 多分隔符
String mixed = "apple,banana;cherry:date";
String[] multi = mixed.split("[,;:]");
System.out.println("多分隔符: " + Arrays.toString(multi));
// [apple, banana, cherry, date]
// 连续分隔符被忽略(默认)
String doubleComma = "a,,b,,,c";
System.out.println("忽略连续: " + Arrays.toString(doubleComma.split(",")));
// [a, b, c]
// 保留尾部空串
String trailing = "a,b,";
System.out.println("默认丢弃尾部: " + Arrays.toString(trailing.split(",")));
// [a, b]
System.out.println("保留尾部: " + Arrays.toString(trailing.split(",", -1)));
// [a, b, ]
// 限制分割次数
String limited = "a,b,c,d";
System.out.println("限制2: " + Arrays.toString(limited.split(",", 2)));
// [a, b,c,d]
}
}小技巧:空白分割用 \\s+ 而非 " ",能处理空格、Tab、换行的各种组合。
java
String text = "a b\t\nc";
String[] parts = text.split("\\s+"); // 正确:匹配任意空白
String[] wrong = text.split(" "); // 错误:只匹配单个空格reset():重置 Matcher
Matcher 被 find() 扫过后会记住位置,要从头开始需要 reset()。
java
public class ResetDemo {
public static void main(String[] args) {
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("aa11bb22cc");
m.find();
System.out.println("第1个: " + m.group()); // 11
m.find();
System.out.println("第2个: " + m.group()); // 22
// ❌ 再找,没了
System.out.println("第3个: " + m.find()); // false
// ✅ 重置后可以重新开始
m.reset();
System.out.println("重置后第1个: " + m.find()); // true, 11
}
}替代方案:重新获取 Matcher,每次 Pattern.matcher() 返回新的 Matcher:
java
// 两种方式等价
m.reset();
// 或者
m = p.matcher(input);常见模式:从实战中来
解析 Key-Value
java
public class ParseKV {
public static void main(String[] args) {
String config = "name=Alice; age=30; city=Beijing";
Pattern kvPattern = Pattern.compile("(\\w+)=(\\w+)");
Matcher m = kvPattern.matcher(config);
Map<String, String> configMap = new HashMap<>();
while (m.find()) {
configMap.put(m.group(1), m.group(2));
}
System.out.println(configMap);
// {name=Alice, age=30, city=Beijing}
}
}提取价格信息
java
public class ExtractPrice {
public static void main(String[] args) {
String ad = "【限时特价】iPhone 15 原价 5999 元,现价只要 4999 元!";
Pattern pricePattern = Pattern.compile("(\\d+)元");
Matcher m = pricePattern.matcher(ad);
if (m.find()) {
int original = Integer.parseInt(m.group(1));
}
if (m.find()) {
int current = Integer.parseInt(m.group(1));
}
System.out.println("原价: " + original + ", 现价: " + current);
System.out.println("节省: " + (original - current) + " 元");
}
}验证并提取
java
public class ValidateAndExtract {
public static void main(String[] args) {
String record = "订单号: ORD-20260322-001, 金额: ¥2999.00";
// 验证格式
Pattern orderPattern = Pattern.compile("订单号: (ORD-\\d{8}-\\d{3}), 金额: ¥(\\d+\\.\\d{2})");
Matcher m = orderPattern.matcher(record);
if (m.matches()) {
String orderId = m.group(1);
BigDecimal amount = new BigDecimal(m.group(2));
System.out.println("订单: " + orderId + ", 金额: " + amount);
}
}
}方法对照表
| 方法 | 作用 | 返回值 |
|---|---|---|
matches() | 整个字符串匹配 | boolean |
find() | 查找下一个匹配 | boolean |
lookingAt() | 从开头匹配 | boolean |
group() | 获取当前匹配 | String |
group(n) | 获取第 n 组 | String |
start() | 匹配起始位置 | int |
end() | 匹配结束位置 | int |
replaceAll(s) | 替换所有 | String |
replaceFirst(s) | 替换第一个 | String |
reset() | 重置位置 | Matcher |
下一节我们来看更方便的 String 正则方法
