Java正则表达式详解(最新整理)

 更新时间:2026年08月18日 08:49:37   作者:程序员黑豆  
在Java中,正则表达式是一种非常强大的工具,用于字符串的搜索、替换、分割等操作,正则表达式由一系列字符组成,这些字符具有特定的含义,用以定义搜索模式,下面是一些基本的正则表达式示例以及如何在Java中使用它们,感兴趣的朋友一起看看吧

这是一个 Java 快速入门项目,非常适合用来练手。相关源码已上传至 GitHub,👉 点击查看 GitHub 仓库。欢迎交流指正、提交 issue。

正则表达式(Regular Expression)用于匹配、查找和替换字符串。Java 通过 java.util.regex 包提供支持。

一、核心类

用途
Pattern编译正则表达式
Matcher执行匹配操作的引擎
PatternSyntaxException语法错误异常

Java 正则表达式主要通过 java.util.regex 包中的 Pattern 和 Matcher 两个类来实现。

1.1 Pattern 类(编译正则表达式)

方法说明
Pattern.compile(String regex)将正则字符串编译为 Pattern 对象
Pattern.compile(String regex, int flags)编译时指定标志(如 CASE_INSENSITIVE
pattern.matcher(String input)用 Pattern 创建 Matcher 对象
pattern.split(String input)按正则分割字符串,返回 String[]

1.2 Matcher 类(执行匹配操作)

方法说明
matches()整个字符串是否完全匹配正则
find()在字符串中查找下一个匹配的子串
lookingAt()字符串开头是否匹配正则
group()返回本次匹配的子串(等同于 group(0)
group(int group)返回第 n 个捕获组的内容
groupCount()返回捕获组的数量
start() / end()返回匹配子串的起始/结束索引
replaceAll(String replacement)替换所有匹配子串
replaceFirst(String replacement)替换第一个匹配子串

1.3 String 类内置方法(日常最常用)

方法说明
str.matches(String regex)判断整个字符串是否匹配(内部调 Pattern.matches
str.replaceAll(String regex, String replacement)替换所有匹配
str.replaceFirst(String regex, String replacement)替换第一个匹配
str.split(String regex)按正则分割字符串
str.split(String regex, int limit)分割,限制最多分成 limit 部分

二、常用正则语法

语法含义示例
.任意字符(除换行)a.b 匹配 a1b
\d数字\d{3} 匹配三位数
\w单词字符\w+ 匹配单词
\s空白字符空格、制表符、换行
*0次或多次a* 匹配 """a""aaa"
+1次或多次\d+ 匹配至少一位数字
?0次或1次colou?r 匹配 colorcolour
{n,m}n到m次\d{2,4} 匹配2~4位数字
^行开头^Hello 匹配行首的 Hello
$行结尾end$ 匹配行尾的 end
[]字符集[aeiou] 匹配任意元音
()分组捕获(\\w+)@(\\w+) 提取用户名和域名

三、简单示例

// 1. 匹配
// 使用 Pattern.matches() 静态方法判断输入字符串是否完全匹配给定的正则表达式。
// "\\d+" 是正则表达式,\d 表示数字 [0-9],+ 表示出现一次或多次,即匹配一个或多个连续数字。
// "123" 是待匹配的目标字符串。由于它完全由数字组成,所以匹配成功,isMatch 的值为 true。
boolean isMatch = Pattern.matches("\\d+", "123"); // true
// 验证手机号
String phone = "13812345678";
boolean valid = phone.matches("^1[3-9]\\d{9}$");
// 2. 查找
// 编译正则表达式:匹配以 a 开头、b 结尾,中间为任意一个字符的模式
Pattern p = Pattern.compile("a.b");
// 创建 Matcher 对象,用于在目标字符串中进行匹配查找
Matcher m = p.matcher("a2b aXb acb");
// 循环查找所有匹配的子串
while (m.find()) {
    // 输出当前匹配到的内容(依次输出 a2b、aXb、acb)
    System.out.println(m.group()); // a2b aXb acb
}
// 3. 替换
// 定义一个包含数字的字符串
String s = "Hello 123 World 456";
// 使用 replaceAll 方法将字符串中所有匹配正则的部分替换为指定文本
// 正则表达式 \\d+ 的含义:
//   - \\d 匹配任意数字字符(0-9),Java 字符串中 \ 需写成 \\
//   - + 表示前面的 \\d 出现 1 次或多次,即匹配连续的数字序列
// 因此 "123" 和 "456" 这两个数字子串都会被替换为 "数字"
String result = s.replaceAll("\\d+", "数字");
// 输出结果:Hello 数字 World 数字
System.out.println(result);
// 4. 分割字符串
String[] parts = "a,b;;c".split("[,;]+"); // ["a", "b", "c"]

小提示:Java 正则中的反斜杠需要写成 \\,因为 Java 字符串本身会先解析一次 \

四、分组捕获示例

分组捕获是正则表达式中非常实用的功能,它允许我们将匹配到的内容按规则拆分成多个独立的部分,便于后续提取和处理。在 Java 中,分组通过圆括号 () 定义,每个左括号对应一个分组编号,编号从 1 开始,group(0) 表示整个匹配结果。

4.1 基本分组捕获

以下示例从日期字符串中分别提取年、月、日:

Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher("日期:2026-06-01");
if (m.find()) {
    System.out.println(m.group(0)); // 2026-06-01(整个匹配)
    System.out.println(m.group(1)); // 2026(年)
    System.out.println(m.group(2)); // 06(月)
    System.out.println(m.group(3)); // 01(日)
}

正则 (\\d{4})-(\\d{2})-(\\d{2}) 中包含了三个分组,分别匹配四位年份、两位月份和两位日期。通过 group(n) 即可按编号取出对应内容,非常直观。

4.2 命名分组(Java 7+)

当分组较多时,数字编号不易记忆。Java 7 引入了命名分组语法 (?<name>...),可以用名称代替编号:

Pattern p = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher m = p.matcher("2026-06-01");
if (m.matches()) {
    System.out.println(m.group("year"));  // 2026
    System.out.println(m.group("month")); // 06
    System.out.println(m.group("day"));   // 01
}

命名分组让代码可读性大幅提升,尤其在处理多个分组时,能避免混淆编号顺序。

4.3 非捕获分组

如果只需要用括号进行逻辑分组,而不想捕获内容,可以使用 (?:...) 语法。非捕获分组不会占用分组编号,也不会产生额外的内存开销:

Pattern p = Pattern.compile("(?:https?://)?(www\\.\\w+\\.com)");
Matcher m = p.matcher("https://www.example.com");
if (m.find()) {
    System.out.println(m.group(1)); // www.example.com(协议部分未被捕获)
}

这里 (?:https?://)? 匹配可选的协议前缀,但不会将其存入分组,group(1) 直接得到域名部分。

4.4 分组嵌套与反向引用

分组可以嵌套,编号按左括号出现的顺序从左到右计数。反向引用 \\n 可以在正则内部引用第 n 个分组已匹配的内容,常用于匹配成对标签或重复单词:

// 匹配 HTML 标签对,如 <div>...</div>
Pattern p = Pattern.compile("<(\\w+)>.*?</\\1>");
Matcher m = p.matcher("<div>内容</div>");
System.out.println(m.matches()); // true

\\1 引用了第一个分组捕获的标签名,确保闭合标签与开始标签一致。

4.5 分组在替换中的应用

分组捕获的内容也可以在字符串替换时通过 $n 或 ${name} 引用:

String date = "2026-06-01";
String reformatted = date.replaceAll(
    "(\\d{4})-(\\d{2})-(\\d{2})",
    "$2/$3/$1"
);
System.out.println(reformatted); // 06/01/2026

这在数据格式转换、日志解析等场景中非常实用,一行代码即可完成复杂的重组操作。

五、正则性能优化建议

5.1 优先使用懒惰模式(Lazy Quantifier)

默认的 *+ 是贪婪模式,会尽可能多地匹配字符,可能导致回溯过多。在量词后加 ? 转为懒惰模式,匹配到第一个满足条件的位置即停止。

// 贪婪模式:可能匹配到最后一个 >,回溯严重
Pattern greedy = Pattern.compile("<.*>");
// 懒惰模式:匹配到第一个 > 即停止,性能更优
Pattern lazy = Pattern.compile("<.*?>");

5.2 预编译并缓存 Pattern 对象

String.matches()String.split() 等每次调用都会重新编译正则,开销极大。应复用预编译的 Pattern 实例,尤其在循环或高频调用场景。

原因分析:

  • String.matches()
    public boolean matches(String regex) {
        return Pattern.matches(regex, this); // 每次调用都重新编译
    }
  • String.split()
    public String[] split(String regex, int limit) {
        // 每次调用都 Pattern.compile(regex)
    }

每次调用都会执行 Pattern.compile(regex),编译阶段需要将正则字符串解析为有限状态自动机(NFA/DFA),这是一个开销较大的过程。

性能差距有多大?

// ❌ 低效写法:循环中反复编译
for (String s : list) {
    s.matches("\\d+");  // 每次都 compile
}
// ✅ 高效写法:预编译,复用 Pattern
Pattern p = Pattern.compile("\\d+");  // 只编译一次
for (String s : list) {
    p.matcher(s).matches();  // 只创建 Matcher,开销极小
}

在循环 100 万次的场景下,预编译写法通常能快 5~10 倍,因为编译开销被均摊掉了。

什么时候需要关心?

场景是否需要预编译
循环/高频调用✅ 必须预编译
只调用一次❌ 无所谓,str.matches() 更简洁
正则固定不变✅ 可以声明为 static final 常量

最佳实践:

public class RegexCache {
	// 正则作为类级别常量,只编译一次
    private static final Pattern EMAIL_PATTERN =
            Pattern.compile("^[\\w.-]+@[\\w.-]+\\.\\w{2,}$");
    public static boolean isValidEmail(String email) {
        return EMAIL_PATTERN.matcher(email).matches(); // 复用预编译 Pattern
    }
}

5.3 拆分复杂正则,降低回溯风险

一个过于复杂的正则表达式难以维护且容易导致灾难性回溯。将其拆分为多个简单正则,分步验证,既提高可读性又提升性能。

// ❌ 一个正则包揽所有校验,回溯风险高
Pattern complex = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,20}$");
// ✅ 拆分为多个简单条件,逐项验证
public static boolean isValidPassword(String pwd) {
    if (pwd.length() < 8 || pwd.length() > 20) return false;
    if (!pwd.matches("[a-zA-Z\\d]+")) return false;  // 仅允许字母数字
    if (!pwd.matches(".*[a-z].*")) return false;     // 至少一个小写
    if (!pwd.matches(".*[A-Z].*")) return false;     // 至少一个大写
    if (!pwd.matches(".*\\d.*")) return false;       // 至少一个数字
    return true;
}

什么时候该拆?什么时候不拆?

场景建议
逻辑可明确分步✅ 拆分
有嵌套/递归结构✅ 拆分(正则本就不擅长)
正则超过一行就难读✅ 拆分
只需简单的格式校验❌ 单个正则即可,别过度设计
对性能极端敏感的热路径⚠️ 需实测,拆分有额外的字符串操作开销

六、实战案例:用户输入验证

下面通过一个完整的邮箱验证示例,展示 Pattern 编译、Matcher 匹配以及验证结果输出的完整流程。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmailValidator {
    // 编译正则表达式为 Pattern 对象(复用)
    private static final Pattern EMAIL_PATTERN =
            Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
    /**
     * 验证邮箱格式是否合法
     * @param email 待验证的邮箱字符串
     * @return true 表示合法,false 表示非法
     */
    public static boolean isValidEmail(String email) {
        if (email == null || email.isEmpty()) {
            return false;
        }
        // 创建 Matcher 对象
        Matcher matcher = EMAIL_PATTERN.matcher(email);
        // 执行匹配并返回结果
        return matcher.matches();
    }
    public static void main(String[] args) {
        // 测试用例
        String[] testEmails = {
                "user@example.com",
                "hello.world@mail.co.uk",
                "invalid-email@",
                "user@.com",
                "name@domain.c",
                "test@sub.domain.com"
        };
        System.out.println("=== 邮箱格式验证结果 ===");
        for (String email : testEmails) {
            boolean result = isValidEmail(email);
            System.out.printf("%-30s → %s%n", email, result ? "✅ 合法" : "❌ 非法");
        }
    }
}

输出结果:

=== 邮箱格式验证结果 ===
user@example.com               → ✅ 合法
hello.world@mail.co.uk         → ✅ 合法
invalid-email@                 → ❌ 非法
user@.com                      → ❌ 非法
name@domain.c                  → ❌ 非法
test@sub.domain.com            → ✅ 合法

代码要点说明:

  • Pattern.compile():将正则表达式编译为 Pattern 对象,作为静态常量复用,避免重复编译开销
  • Pattern.matcher():创建 Matcher 对象,绑定待匹配的输入字符串
  • matcher.matches():尝试将整个输入字符串与正则进行完全匹配,返回 boolean 结果
  • 空值检查:先对输入做 null 和空字符串判断,避免 NullPointerException

七、注意事项

  • . 默认不匹配换行符,可用 Pattern.DOTALL 改变
  • Java 字符串中反斜杠需转义,如 \d 写成 "\\d"
  • 大量重复匹配时复用 Pattern,避免每次调用 String.matches() 重新编译
  • 常用标志:Pattern.CASE_INSENSITIVE(忽略大小写)、Pattern.MULTILINE(多行模式)

到此这篇关于Java正则表达式详解的文章就介绍到这了,更多相关Java正则表达式详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot AOP导致service注入后是null的问题

    SpringBoot AOP导致service注入后是null的问题

    本文主要讲述了如何利用SpringAOP实现用户操作日志的记录,首先,引入SpringBoot的AOP依赖,然后,选择基于注解的形式来实现日志操作,以避免污染原有代码和逻辑,在理解了SpringBootAOP的一些注解后,需要记录用户的正常请求以及异常请求的信息
    2024-10-10
  • MyBatis映射文件resultMap元素中使用多个association的方法

    MyBatis映射文件resultMap元素中使用多个association的方法

    这篇文章主要介绍了MyBatis映射文件resultMap元素中使用多个association的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Spring populateBean属性赋值和自动注入

    Spring populateBean属性赋值和自动注入

    这篇文章主要为大家介绍了Spring populateBean属性赋值和自动注入示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Java注解之Elasticsearch的案例详解

    Java注解之Elasticsearch的案例详解

    学会了技术就要使用,否则很容易忘记,因为自然界压根就不存在什么代码、变量之类的玩意,这都是一些和生活常识格格不入的东西。这篇文章主要介绍了Java中Elasticsearch的案例,感兴趣的可以了解一下
    2022-10-10
  • SpringSession会话管理之Redis与JDBC存储实现方式

    SpringSession会话管理之Redis与JDBC存储实现方式

    本文将详细介绍Spring Session的核心概念、特性以及如何使用Redis和JDBC来实现会话存储,帮助开发者构建更加健壮和可扩展的应用系统,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Springboot配置返回日期格式化五种方法详解

    Springboot配置返回日期格式化五种方法详解

    本文主要介绍了Springboot配置返回日期格式化五种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • SpringBoot2零基础到精通之数据库专项精讲

    SpringBoot2零基础到精通之数据库专项精讲

    SpringBoot是一种整合Spring技术栈的方式(或者说是框架),同时也是简化Spring的一种快速开发的脚手架,本篇我们来学习如何连接数据库进行操作
    2022-03-03
  • Java读取传输FTP文件实现示例

    Java读取传输FTP文件实现示例

    本文主要介绍了Java读取传输FTP文件方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Java 重写与重载方法与区别详解

    Java 重写与重载方法与区别详解

    本篇文章通过实例详细介绍了重写与重载,以及他们的区别,需要的朋友可以参考下
    2017-04-04
  • Java对象数组定义与用法详解

    Java对象数组定义与用法详解

    这篇文章主要介绍了Java对象数组定义与用法,结合实例形式分析了java对象数组的概念、功能、定义与使用方法,需要的朋友可以参考下
    2019-08-08

最新评论