Skip to content

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&lt;String, String&gt; configMap = new HashMap&lt;&gt;();
        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 正则方法

基于 VitePress 构建