杰瑞科技汇

Java正则表达式group如何正确使用与区分?

group 是正则表达式一个非常强大和核心的功能,它允许你:

  1. 匹配和捕获子串:从整个匹配的字符串中,提取出你感兴趣的特定部分。
  2. 组织和复用模式:使用括号 将一部分模式组合成一个单元,并可以对这个单元应用量词(如 , , {n,m})。
  3. 反向引用:在同一个正则表达式中,让后面的部分匹配前面捕获组的内容。

基本概念:什么是捕获组?

捕获组是通过在正则表达式中使用括号 来创建的,括号内的部分就是一个子表达式,它会形成一个“组”。

语法: (pattern)

示例: 假设我们要匹配一个格式为 "单词-单词" 的字符串,"apple-orange"。

  • 没有捕获组:

    String regex = "\\w+-\\w+"; // 匹配 "apple-orange"

    这个正则表达式可以匹配整个字符串,但无法单独提取出 "apple" 或 "orange"。

  • 使用捕获组:

    String regex = "(\\w+)-(\\w+)"; // 匹配 "apple-orange"

    这个正则表达式创建了两个捕获组

    • Group 1: (\w+),用于匹配第一个单词。
    • Group 2: (\w+),用于匹配第二个单词。
    • 整个表达式匹配的结果被称为 Group 0(或整个匹配)。

在 Java 中如何使用 group

Java 中处理正则表达式主要使用 java.util.regex 包下的 PatternMatcher 类。

核心方法

Matcher 类提供了以下方法来访问捕获组:

  • group(): 返回整个匹配的字符串(即 Group 0)。
  • group(int groupIndex): 返回指定索引的捕获组所匹配的子串。
  • groupCount(): 返回此匹配器的模式中的捕获组总数。

索引规则:

  • Group 0: 整个匹配,索引从 0 开始。
  • Group 1, 2, 3, ...: 由 创建的捕获组,索引从 1 开始,按照左括号出现的顺序递增。

代码示例

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexGroupExample {
    public static void main(String[] args) {
        String text = "我的电话是 138-1234-5678,备用电话是 186-8765-4321。";
        String regex = "(\\d{3})-(\\d{4})-(\\d{4})"; // 一个区号,一个前四位,一个后四位
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        // 查找所有匹配项
        while (matcher.find()) {
            System.out.println("找到匹配: " + matcher.group()); // Group 0: 整个电话号码
            // 获取各个捕获组
            System.out.println("  完整号码: " + matcher.group(0)); // Group 0
            System.out.println("  区号:     " + matcher.group(1)); // Group 1
            System.out.println("  前四位:   " + matcher.group(2)); // Group 2
            System.out.println("  后四位:   " + matcher.group(3)); // Group 3
            System.out.println("  总捕获组数: " + matcher.groupCount()); // 3
            System.out.println("--------------------");
        }
    }
}

输出:

找到匹配: 138-1234-5678
  完整号码: 138-1234-5678
  区号:     138
  前四位:   1234
  后四位:   5678
  总捕获组数: 3
--------------------
找到匹配: 186-8765-4321
  完整号码: 186-8765-4321
  区号:     186
  前四位:   8765
  后四位:   4321
  总捕获组数: 3
--------------------

特殊类型的捕获组

除了标准的命名捕获组,还有一些特殊类型的组。

a) 非捕获组

你只想用括号来组织表达式,但不关心这个组匹配的内容,也不希望它被计入 groupCount()

语法: (?:pattern)

示例: 匹配 "apple-orange" 或 "banana-orange",并提取水果名和 "orange"。

String text = "I like apple-orange and banana-orange.";
// (\\w+) 匹配水果名
// (?:...)-orange 是一个整体,但我们只关心水果名和 "orange"
String regex = "(\\w+)(?:-(\\w+))?";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
    System.out.println("匹配: " + matcher.group(0));
    System.out.println("  水果名: " + matcher.group(1)); // 总是第一个单词
    // group(2) 可能为 null,因为第二个括号是可选的
    System.out.println("  第二部分: " + (matcher.group(2) != null ? matcher.group(2) : "N/A"));
    System.out.println("  总捕获组数: " + matcher.groupCount()); // 只有2个捕获组
}

在这个例子中, 帮助我们实现了 (A)-(B)(A) 的模式,但 B 不会被捕获为一个独立的组(索引2)。groupCount() 只计算 (\w+) 这一个捕获组。

b) 命名捕获组 (?<name>...)(?P<name>...)

当正则表达式非常复杂,有很多捕获组时,用数字(1, 2, 3...)来引用会变得很混乱,命名捕获组允许你给每个组起一个有意义的名字,通过名字来访问。

语法 (Java 风格): (?<name>pattern)

语法 (PCRE 风格,也受 Java 支持): (?P<name>pattern)

访问方法:

  • matcher.group("name")

示例: 提取日期,并按名称访问年、月、日。

String text = "今天的日期是 2025-10-27。";
String regex = "\\b(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})\\b";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
    System.out.println("找到日期: " + matcher.group()); // Group 0
    System.out.println("  年: " + matcher.group("year"));
    System.out.println("  月: " + matcher.group("month"));
    System.out.println("  日: " + matcher.group("day"));
}

输出:

找到日期: 2025-10-27
  年: 2025
  月: 10
  日: 27

这种方式代码可读性更高,也更不容易出错。


高级用法:反向引用

反向引用允许你在正则表达式的后续部分,匹配前面某个捕获组已经匹配到的内容。

语法:

  • 在正则表达式中:\数字 (\1, \2)
  • StringreplaceAll 方法中:$数字 ($1, $2)

示例 1:在正则表达式中使用

匹配两个连续相同的单词,"hello hello"。

String text = "This is a test test sentence. But this is not a good good example.";
// (\\w+) 匹配一个单词,并捕获为 Group 1
// \\s+ 匹配一个或多个空格
// \\1 匹配 Group 1 的内容(即第一个单词)
String regex = "(\\w+)\\s+\\1";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
    System.out.println("找到重复单词: " + matcher.group(0) + " (单词是: " + matcher.group(1) + ")");
}

输出:

找到重复单词: test test (单词是: test)

这里的 \1 就是指向第一个捕获组 (\w+) 的内容。

示例 2:在 String.replaceAll 中使用

假设我们想把 "apple,orange,banana" 这样的字符串格式化为 "apple | orange | banana"。

String text = "apple,orange,banana";
// (\\w+) 匹配一个单词,并捕获为 Group 1
String regex = "(\\w+),"; // 匹配 "word,"
// $1 指向第一个捕获组的内容
String result = text.replaceAll(regex, "$1 | ");
System.out.println(result);

输出:

apple | orange | banana|

注意最后多了一个 ,因为正则表达式会匹配最后一个 "banana,",但实际上 "banana" 后面没有逗号,replaceAll 不会对 "banana" 做任何处理,一个更精确的写法是:

String regex = "(\\w+),"; // 匹配 "word,"
// 使用边界 \\b 确保只匹配单词后的逗号
String result = text.replaceAll("\\b(\\w+),", "$1 | ");
System.out.println(result); // apple | orange | banana

重要注意事项

  1. IllegalStateException:在调用 group()group(int) 方法之前,必须先调用 find()matches() 方法,如果没有找到任何匹配,调用这些方法会抛出 IllegalStateException

  2. IndexOutOfBoundsException:如果尝试访问一个不存在的组索引(group(5)groupCount() 返回 3),会抛出 IndexOutOfBoundsException

  3. 嵌套组:捕获组可以嵌套,在这种情况下,组的编号是按照左括号出现的顺序来决定的。 在 ((A)(B(C))) 中:

    • Group 1: (A)(B(C))
    • Group 2: (A)
    • Group 3: (B(C))
    • Group 4: (C)
功能 正则语法 Java 代码 描述
标准捕获组 matcher.group(1) 捕获子串,按数字索引从1开始访问。
非捕获组 matcher.groupCount() 不计数 组织模式,但不捕获内容,不计入组数。
命名捕获组 (?<name>...) matcher.group("name") 为组命名,通过名称访问,提高可读性。
反向引用 \数字 (在 regex 中) - 在正则表达式中匹配前面捕获组的内容。
反向引用 $数字 (在 replaceAll 中) text.replaceAll(..., "$1") 在替换字符串中引用捕获组的内容。

掌握 group 是精通 Java 正则表达式的关键,从简单的 到复杂的命名和反向引用,它能让你灵活地处理各种复杂的文本匹配和提取任务。

分享:
扫描分享到社交APP
上一篇
下一篇