1. 概述

在实际开发中,经常会遇到需要校验字符串格式的场景,比如邮箱、身份证、手机号等。本文聚焦于如何使用 Java 正则表达式(Regular Expressions)来验证常见的手机号码格式。

虽然手机号格式因国家而异,但我们可以从简单到复杂,逐步构建出能覆盖多种格式的正则表达式。✅ 掌握这些技巧,能帮你少踩不少坑。

2. 验证手机号的正则表达式

2.1 十位纯数字号码

最简单的场景:验证一个字符串是否为 恰好10位数字,不包含任何其他字符。

@Test
public void whenMatchesTenDigitsNumber_thenCorrect() {
    Pattern pattern = Pattern.compile("^\\d{10}$");
    Matcher matcher = pattern.matcher("2055550125");
    assertTrue(matcher.matches());
}

📌 解析:

  • ^ 表示字符串开始
  • \\d{10} 表示连续10个数字
  • $ 表示字符串结束

✅ 匹配示例:2055550125

⚠️ 注意:这种写法非常严格,不允许空格、横线等分隔符。


2.2 支持空格、点号或横线分隔

现实中用户输入更自由,比如 202 555 0125202.555.0125202-555-0125。我们可以通过添加可选分隔符来增强匹配能力。

@Test
public void whenMatchesTenDigitsNumberWhitespacesDotHyphen_thenCorrect() {
    Pattern pattern = Pattern.compile("^(\\d{3}[- .]?){2}\\d{4}$");
    Matcher matcher = pattern.matcher("202 555 0125");
    assertTrue(matcher.matches());
}

📌 解析:

  • \\d{3}[- .]?:匹配3位数字 + 可选的一个空格、.-
  • {2}:前面的结构重复两次(即前6位)
  • \\d{4}:最后4位数字

✅ 支持格式:

  • 2055550125
  • 202 555 0125
  • 202.555.0125
  • 202-555-0125

💡 小技巧:[- .] 中的空格代表真正的空格字符,顺序无关紧要。


2.3 支持括号格式

有些用户习惯把区号用括号括起来,例如 (202) 555-0125。我们需要让正则支持这种写法。

@Test
public void whenMatchesTenDigitsNumberParenthesis_thenCorrect() {
    Pattern pattern = Pattern.compile("^((\\(\\d{3}\\))|\\d{3})[- .]?\\d{3}[- .]?\\d{4}$");
    Matcher matcher = pattern.matcher("(202) 555-0125");
    assertTrue(matcher.matches());
}

📌 解析:

  • (\\(\\d{3}\\)):匹配 (202) 这种带括号的3位数字(注意括号要转义)
  • |:或操作
  • \\d{3}:或者直接3位数字
  • 外层 (...) 将两者组合成一个整体

✅ 支持格式:

  • (202)5550125
  • (202) 555-0125
  • (202)-555-0125
  • 也兼容前面所有无括号格式

⚠️ 踩坑提醒:正则中 () 是特殊字符,必须写成 \\(\\) 才能匹配字面意义的括号。


2.4 支持国际区号前缀

全球化的应用中,手机号可能带有国际前缀,如 +1+86 等。通常前缀以 + 开头,后跟1-3位国家代码。

@Test
public void whenMatchesTenDigitsNumberPrefix_thenCorrect() {
  Pattern pattern = Pattern.compile("^(\\+\\d{1,3}( )?)?((\\(\\d{3}\\))|\\d{3})[- .]?\\d{3}[- .]?\\d{4}$");
  Matcher matcher = pattern.matcher("+111 (202) 555-0125");
  
  assertTrue(matcher.matches());
}

📌 解析:

  • (\\+\\d{1,3}( )?)?:整个国际前缀是可选的(末尾的 ?
    • \\+:匹配 + 符号
    • \\d{1,3}:1到3位数字
    • ( )?:可选的空格
  • 后半部分沿用之前的逻辑

✅ 支持格式:

  • +1 202 555 0125
  • +86(10)12345678
  • +111 202.555.0125

💡 实际项目中,可根据业务需要调整国家代码位数限制。

3. 组合多个正则表达式

有时候单一正则无法覆盖所有合法格式。比如除了北美格式,你还想支持欧洲常见的分组方式(如 +44 20 7946 0958),这时可以 将多个正则用 |(或)连接,形成一个“组合正则”。

示例场景

我们想同时支持以下三种模式:

  1. 北美标准格式(含括号、分隔符等)
  2. 纯数字三三分组:+111 123 456 789
  3. 欧洲风格分组:+111 123 45 67 89
@Test
public void whenMatchesPhoneNumber_thenCorrect() {
    String patterns 
      = "^(\\+\\d{1,3}( )?)?((\\(\\d{3}\\))|\\d{3})[- .]?\\d{3}[- .]?\\d{4}$" 
      + "|^(\\+\\d{1,3}( )?)?(\\d{3}[ ]?){2}\\d{3}$" 
      + "|^(\\+\\d{1,3}( )?)?(\\d{3}[ ]?)(\\d{2}[ ]?){2}\\d{2}$";

    String[] validPhoneNumbers 
      = {"2055550125","202 555 0125", "(202) 555-0125", "+111 (202) 555-0125", 
      "636 856 789", "+111 636 856 789", "636 85 67 89", "+111 636 85 67 89"};

    Pattern pattern = Pattern.compile(patterns);
    for(String phoneNumber : validPhoneNumbers) {
        Matcher matcher = pattern.matcher(phoneNumber);
        assertTrue(matcher.matches());
    }
}

📌 关键点:

  • 每个子表达式独立编写,逻辑清晰
  • | 连接,表示“任意一个匹配即成功”
  • 注意转义和字符串拼接时的引号处理

✅ 优势:

  • 灵活扩展:新增格式只需加一个 |...
  • 易于维护:每个分支职责单一

❌ 缺点:

  • 正则变长后可读性下降
  • 性能略低于单一优化正则(但通常可忽略)

💡 建议:如果业务中手机号格式较复杂,推荐先写多个小正则测试通过后,再合并使用。

4. 总结

本文通过由浅入深的方式,展示了如何使用 Java 正则表达式验证手机号码:

  • 从最简单的10位纯数字开始
  • 逐步增加对分隔符、括号、国际前缀的支持
  • 最后通过 | 操作符组合多个正则,覆盖更广的格式

📌 实际项目建议:

  • 不要过度追求“万能正则”,容易变得难以维护
  • 结合业务需求选择合适复杂度的校验规则
  • 对于高要求场景,可考虑使用专门的库如 Google's libphonenumber

示例代码已上传至 GitHub:https://github.com/example/java-regex-phone-validator


原始标题:Validate Phone Numbers With Java Regex