Java 正则表达式实战指南:从基础语法到进阶技巧

在日常开发中,字符串处理是高频需求,而正则表达式(Regular Expression,简称 Regex)就是处理文本的利器。Java 提供了功能强大的 PatternMatcher 类来支持正则表达式操作,可以帮助我们完成 匹配、查找、替换、分割 等任务。

本文将从 基础语法 开始,逐步带你掌握 Java 中的正则表达式,并通过实例展示实际应用场景。


一、正则表达式基础语法

正则表达式语法规则较多,不急于一时全都记住,可以大概有个印象,用到时来找对应的具体语法;通过几个具体案例实践练习一下,可以帮助我们更快的记忆这些规则。

1. 字符类

[abc]       	只能是 a 或 b 或 c
[^abc]      	除了 a, b, c 之外的任何字符
[a-zA-Z]    	a 到 z 或 A 到 Z
[a-d[A-Z]]  	a 到 d 或 A 到 Z
[a-z&&[def]]    a-z 与 def 的交集 → d, e, f
[a-z&&[^bc]]    a-z 中去掉 b, c
[a-z&&[^m-p]]   a-z 中去掉 m 到 p

要点快记:

  1. 方括号[ ] 里的元素只能出现一次;
  2. ^X :表示除了X之外的其他元素;
  3. && :表示 ‘和’ 的意思,两边条件需要同时满足。

2. 预定义字符

.       任意字符
\d      数字 [0-9]
\D      非数字
\s      空白字符
\S      非空白字符
\w      单词字符 [a-zA-Z_0-9]
\W      非单词字符

⚠️ 在 Java 字符串中常见 \\d,因为在代码中 \ 本身需要转义。

3. 数量词

X?      出现 0 或 1 次
X*      出现 0 次或多次
X+      出现 1 次或多次
X{n}    出现 n 次
X{n,}   至少出现 n 次
X{n,m}  出现 n 到 m 次

要点快记:

  1. { }用来自定义次数;
  2. X?表示 “0或1次”(联想辅助记忆:?疑问有没有出现,有/无 —> 1/0)
  3. X+ 和 X*(联想辅助: + 为正,正数不包括0,即X+表示 “1次或多次”;X* 则表示 “0次或多次” )。

4. 常用正则工具推荐

实际情况中,可以使用插件找到类似的正则表达式进行修改,如any_rule插件,里面包含各类常用的regex,如身份证号,手机号码等,以提高代码效率。
在这里插入图片描述


二、Pattern 与 Matcher 的使用

知识点

Java 提供了以下两个核心类:

  • Pattern:正则表达式的编译表示
  • Matcher:文本匹配器,用来在字符串中查找符合规则的子串

我们以一个实例了解上述方法在具体场景的用法。

案例

示例:提取一段文字中的 Java 版本号。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZhengZhe {
    public static void main(String[] args) {
        //从一段文字中提取想要的东西: Java 版本号
        String str = "Java自从95年问世依赖,经历了很多版本,目前企业中常用的 是Java8和Java11,"+
                "因为这两个是长期支持版本,下一个长期支持版本的是Java17,相信在未来不久Java17也会逐渐登上历史的舞台";

        //1.获取正则表达式的对象
        Pattern p = Pattern.compile("Java\\d{0,2}");

        //2. 获取文本匹配器的对象
        Matcher m = p.matcher(str);
        //拿着m去读str,找符合p规则的子串

        Method(str);
        //这个方法内部可以帮助理解 m.find() 与 m.group() 工作原理
        System.out.println("--------------------");

        //3.利用循环获取
        while(m.find()){
            String s = m.group();
            System.out.println(s);  //Java Java8 Java11 Java17 Java17
        }
    }
    public static void Method(String str){

        /*
        m.find() 与 m.group() 工作原理:
         */

        Pattern  p = Pattern.compile("Java\\d{0,2}");

        Matcher m = p.matcher(str);
        boolean b = m.find();
        //原理:拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
        //	无,返回false;   有,返回true,在底层记录了子串的起始索引和结束索引+1

        String s1 = m.group();
        System.out.println(s1); // Java
        //原理:方法底层根据find方法记录的索引进行字符串的截取:subString(起始索引,结束索引),包头不包尾;如,(0,4),不包含4索引
        //会把截取的小串进行返回

        b = m.find();
        //第二次调用find,方法会继续往后读,直到找到第二个子串返回true/没找到返回false
        String s2 = m.group();
        System.out.println(s2); // Java8
    }
}

输出:

Java
Java8
-------------------------
Java
Java8
Java11
Java17
Java17

三、进阶技巧与应用案例

本章节通过实际案例介绍以下文本处理方法:零宽断言、贪婪/非贪婪匹配、字符串替换(replaceAll)与分割(split)、捕获分组与非捕获分组。

1. 零宽断言 & 贪婪爬取与非贪婪爬取

知识点讲解
(1) 零宽断言(Lookaround)

零宽断言分为 4 种:

  • 正向前瞻 (?=X):匹配后面紧跟着 X 的位置
    示例:\d+(?=元) → 匹配数字,要求数字后面必须是“元”

  • 负向前瞻 (?!X):匹配后面不是 X 的位置
    示例:\d+(?!元) → 匹配数字,要求数字后面不能是“元”

  • 正向后顾 (?<=X):匹配前面是 X 的位置
    示例:(?<=@)\w+ → 匹配 @ 后面的单词字符

  • 负向后顾 (?<!X):匹配前面不是 X 的位置
    示例:(?<!@)\w+ → 匹配前面不是 @ 的单词字符

(2) 贪婪匹配与非贪婪匹配
  • 正则中的数量词默认是 贪婪模式,会尽可能多地匹配字符。例如:
".+"

在字符串 “abc def” 上匹配时,会得到整个 “abc def”。

  • 如果改为 非贪婪模式(在数量词后加 ?),则会尽可能少地匹配字符:
".+?"

在 “abc def” 上匹配时,结果是 “abc”(遇到第一个空格就停止)。

案例

案例:对指定字符串按照需求进行文本处理。
需求1:提取版本号为8、11、17的Java文本(不显示版本号)
需求2:提取版本号为8、11、17的Java文本(包含版本号)
需求3:提取版本号非8、11、17的其他Java文本
需求4:正则表达式匹配模式:非贪婪匹配文中的ab;贪婪匹配文中中的abbb…

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZhengZe2 {
    public static void main(String[] args) {
        /*
        对下面一段文字提取指定内容
        需求1:爬取版本号为8,11,17的java,但不输出版本号;
        需求2:爬取版本号为8,11,17的java,并且输出版本号
        需求3:爬取除了版本号为8,11,17的其他Java文本
        需求4:贪婪爬取ab+(Java默认是贪婪爬取),非贪婪爬取ab,正则:(ab+?)
         */

        String str = "Java自从95年问世依赖,经历了很多版本,目前企业中常用的 是Java8和Java11,abbbbbbbbbbbbbbbbbbaaaaaaa"+
                "因为这俩呢个是长期支持版本,下一个长期支持版本的是Java17,相信在未来不久Java17也会逐渐登上历史的舞台";

        Pattern p1 = Pattern.compile("((?i)Java)(?=8|11|17)"); //需求1
        /*
        最前面的(?i)是忽略大小写的意思
        ?=  的解释(辅助记忆):
        ?可以看作是指代前面的Java
        = 表示在Java后面要跟随的数据,但在获取的时候,只获取前半部分
         */
        Pattern p2 = Pattern.compile("((?i)Java)(?:8|11|17)"); //需求2
//        Pattern p2 = Pattern.compile("((?i)Java)(8|11|17)"); //需求2
        Pattern p3 = Pattern.compile("((?i)Java)(?!8|11|17)"); //需求3
        
        Pattern p4 = Pattern.compile("ab+?");//需求4
        Pattern p5 = Pattern.compile("ab+");

        Matcher m1 = p1.matcher(str);
        while(m1.find()){
            String s1 = m1.group();
            System.out.println(s1); // Java Java Java Java
        }
        System.out.println("--------------------");

        Matcher m2 = p2.matcher(str);
        while(m2.find()){
            String s2 = m2.group();
            System.out.println(s2); //Java8 Java11 Java17 Java17
        }
        System.out.println("--------------------");

        Matcher m3 = p3.matcher(str);
        while(m3.find()){
            String s3 = m3.group();
            System.out.println(s3);//Java
        }

			 Matcher m4 = p4.matcher(str);
        while(m4.find()){
            String s4 = m4.group();
            System.out.println(s4); //ab
        }
        Matcher m5 = p5.matcher(str);
        while(m5.find()){
            String s5 = m5.group();
            System.out.println(s5); //abbbbbbbbbbbbbbbbbb
        }
    }
}

小结:

零宽断言 允许我们定义“必须匹配的上下文条件”,但不把条件本身包含进结果。

贪婪模式 会尽量多匹配,非贪婪模式 会尽量少匹配。


2. 字符串替换与切割

本节介绍两个常用识别正则的方法:replaceAll、split。

    public String replaceAll(String regex,String newStr)    按照正则表达式的规则进行替换
    public String[] split(String regex)                     按照正则表达式规则切割字符串

我们通过案例来学习这两个方法的使用。
要求1:将下面一段字符串中的杂乱字母数字换成vs
要求2:将字符串中的三个姓名分割出来

替换:replaceAll
String s = "喜羊羊dfhlufbkjcsdi666美羊羊kjfhurecbiiad888懒羊羊";
String result = s.replaceAll("[\\w&&[^_]]+", "vs");
System.out.println(result);
//底层代码原理:先创建文本编辑器对象,从头读取字符串中的内容,只要有满足的,就用第二个参数去替换。

输出:

喜羊羊vs美羊羊vs懒羊羊
切割:split
String[] arr = s.split("[\\w&&[^_]]+");
for (String name : arr) {
    System.out.println(name);
}

输出:

喜羊羊
美羊羊
懒羊羊

四、分组与引用:捕获分组与非捕获分组

在正则表达式中,分组(Grouping) 能够让我们将部分表达式用 () 括起来,形成独立单元,从而实现 引用、复用或逻辑分块
分组分为两大类:捕获分组(Capturing Group)非捕获分组(Non-Capturing Group)


1. 捕获分组

捕获分组的特点是:

  • 默认行为() 会自动捕获内容,并分配一个组号。

  • 组号分配规则:从左到右,最左边的 ( 为第 1 组,依次递增。

  • 分组内容复用

    • 在正则内部可用 \\组号 引用
    • 在正则外部替换时可用 $组号 引用
案例 1:判断字符串首尾字符是否一致
String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1)); // true
System.out.println("2344i".matches(regex1)); // false

解释:

  • (.) 捕获首字符
  • \\1 引用第 1 组,即首字符
  • .+ 表示中间可以有任意字符

👉 只要首尾字符一致,就能匹配成功。


案例 2:判断字符串首尾子串是否一致
String regex2 = "(.+).+\\1";
System.out.println("abc123abc".matches(regex2)); // true
System.out.println("b456b".matches(regex2));     // true
System.out.println("ab123ba".matches(regex2));   // false

解释:

  • (.+) 捕获开头的一段字符串
  • \\1 表示结尾部分必须与开头捕获的内容一致

案例 3:开头连续字符 + 结尾连续字符判断
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3)); // true
System.out.println("aba123baa".matches(regex3)); // true
System.out.println("ab123bc".matches(regex3));   // false

解释:

  • (.) 捕获首字符(记为第 2 组)
  • \\2* 表示重复该字符多次
  • 整体 ((.)\\2*) 捕获连续的相同字符(记为第 1 组)
  • \\1 要求结尾部分与开头连续字符相同

案例 4:去除重复字符
String str = "我要学学编编编程程程程。";
String result = str.replaceAll("(.)\\1+","$1");
System.out.println(result); // 我要学编程。

解释:

  • (.) 捕获一个字符
  • \\1+ 表示该字符至少再重复一次
  • $1 表示替换时只保留第一次捕获的字符

👉 实现“连续字符去重”的效果。


2. 非捕获分组

非捕获分组的特点是:

  • 语法形式:(?:pattern)(?=pattern)(?!pattern)

  • 不占用组号,仅用于逻辑分组或条件约束。

  • 常见用法:

    • (?:abc):仅作为整体匹配,不记录分组
    • (?=abc):正向前瞻,匹配后面必须是 abc
    • (?!abc):负向前瞻,匹配后面不能是 abc

👉 它在前文的“零宽断言”部分已详细介绍。


3. 小结

  1. 捕获分组(默认):
  • 自动分配组号,从 1 开始
  • 可在正则内部用 \\n 引用,在外部用 $n 引用
  • 常用于判断开头和结尾是否一致、字符串替换等场景
  1. 非捕获分组
  • 不分配组号,仅作逻辑分组或上下文约束
  • 常见形式:(?:...)(?=...)(?!...)
  1. 应用技巧
  • 判断首尾一致 → 捕获分组 + \\n 引用
  • 去除重复字符 → 捕获分组 + $n 替换
  • 条件文本提取 → 非捕获分组 + 零宽断言

四、总结

本文系统梳理了 Java 正则表达式的基础语法与常见用法,从字符类、预定义字符、数量词入手,逐步延伸到 Pattern/Matcher 的使用。文章重点讲解了 零宽断言、贪婪与非贪婪匹配、字符串替换与切割、分组与引用 等实用技巧,并辅以案例代码进行演示。通过学习,读者可以掌握 Java 正则的核心方法和应用场景,在实际开发中高效完成文本提取、替换与验证等任务。

正则表达式的威力强大,但也容易写得晦涩。掌握常见模式,并通过实际案例练习,才能真正发挥它在 Java 开发中的价值。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐