首页 > 编程笔记 > Java笔记 阅读:16

Java正则表达式用法详解(附带实例)

正则表达式是由一系列字符组合而成的用于描述某种搜索匹配模式的表达式,它是一种非常强大的字符串处理工具,通常用于对字符串的搜索和提取。

正则表达式属于计算机科学领域中的概念,实际上它是一套模式匹配的规则语法,与具体的编程语言无关,常见的 Java、PythonC++ 以及 JavaScript 等编程语言都支持正则表达式。

那么为什么需要正则表达式呢?有些时候我们需要对一些重要的信息进行匹配,比如邮箱地址、电话号码、密码等,我们经常会在系统注册时见到这些信息,通常系统会对它们的合法性进行验证。那么该如何判断用户输入的手机号和邮箱地址是否合法呢?

以手机号为例,用户可能会随意输入“020-8488674”、“1373944sd23”、“12312312312312312”等。我们很快会想到验证合法的手机号的一些判断规则,比如长度为 11、以“1”开头、必须全部为数字。

根据这些规则我们可以编写出判断代码,例如:
public class PhoneNumber {
    public static void main(String[] args) {
        System.out.println(isValidPhoneNumber("13760708870"));
        System.out.println(isValidPhoneNumber("137607080870"));
        System.out.println(isValidPhoneNumber("137607sdf70"));
        System.out.println(isValidPhoneNumber("33760745670"));
    }

    public static boolean isValidPhoneNumber(String number) {
        if (number.length() != 11)
            return false;
        if (!number.startsWith("1"))
            return false;
        for (int i = 0; i < number.length(); i++) {
            if (number.charAt(i) < '0' || number.charAt(i) > '9') {
                return false;
            }
        }
        return true;
    }
}

但是这些判断规则太烦琐了,有没有简单的解决方法呢?答案是用正则表达式,只需一行代码就能搞定。例如:
public static boolean isValidPhoneNumber2(String number) {
    return number.matches("1\\d{10}");
}
程序中,“1\\d{10}”就是正则表达式,表示“以 1 开头且接着的 10 个字符都是数字”。

为了便于进行字符串处理,String 类提供了对正则表达式的支持,包括下表中的几个常用方法:

表:String类的支持正则表达式的方法
方法名 描述
boolean matches(String regex) 检测当前字符串是否匹配正则表达式
String replaceAll(String regex, String replacement) 把当前字符串中所有能匹配正则表达式的子字符串都替换成指定的字符串
String[] split(String regex) 按照正则表达式将当前字符串分割成若干子字符串

下面我们通过 JShell 快速验证正则匹配结果。在下面程序中,对于字符串 “aaaabbbb”,正则表达式“(a|b)*”表示匹配 0 个及以上的字符 a 或 b,“\\d+”表示匹配一个及以上的数字。
jshell> String s = "aaaabbbb";
s ==> "aaaabbbb"

jshell> s.matches("(a|b)*");
$3 ==> true

jshell> s.matches("\\d+")
$4 ==> false

然后看匹配替换的例子:
jshell> String s = "你好123哈哈";
s ==> "你好123哈哈"

jshell> s.replaceAll("\\d","n");
$8 ==> "你好nnn哈哈"

jshell> String s = "我打开了www.baidu.com网站";
s ==> "我打开了www.baidu.com网站"

jshell> s.replaceAll("www(.*)com","xxx");
$16 ==> "我打开了xxx网站"
对于字符串“你好123哈哈”,正则表达式“\\d”匹配的是数字,所以会将数字替换成“n”字符;对于字符串“我打开了www.baidu.com网站”,正则表达式“www(.*)com”匹配的是网址,所以会将网址替换成“xxx”字符串。

再看字符串分割的操作:
jshell> String s = "aa12bb43cc66dd88ee";
s ==> "aa12bb43cc66dd88ee"

jshell> s.split("\\d+");
$19 ==> String[5] { "aa", "bb", "cc", "dd", "ee" }
程序中,对于字符串“aa12bb43cc66dd88ee”,正则表达式“\\d+”匹配的是一个或多个数字,所以会根据数字来分割字符串,最终将原来的字符串分割成 5 个子字符串。

通过上面正则表达式的使用我们体会到了使用正则表达式的便利,它的强大之处在于提供了强有力的表达语法,通过短短的语法表达式便能对复杂的规则进行描述。

Java匹配单个字符

下表是字符匹配的语法:

表:单字符匹配
字符 说明
某个普通字符 匹配指定的普通字符,比如 A 会匹配 A
\u四个十六进制数值 匹配指定十六进制数所表示的 Unicode 字符
\t 匹配制表符
\n 匹配换行符
\r 匹配回车符
\f 匹配换页符
\e 匹配 Escape 符

对于普通字符,直接由字符本身进行匹配,比如 A 匹配 A,B 匹配 B。但对于一些特殊的字符,如果想匹配就需要特别表示,比如 Unicode 字符、回车符、制表符等。

下面是字符匹配的示例:
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("A".matches("A")); //true
        System.out.println("A".matches("B")); //false
        System.out.println("你".matches("\u4f60")); //true
        System.out.println(" ".matches("\\t")); //false
    }
}
由于换行符、回车符、换页符、Escape 符等不方便描述,因此这里我们不列出这些例子。此外,我们发现在 Java 代码中使用了两个反斜杠(\\),这是因为 Java 语言层面规定了用两个反斜杠来表示反斜杠,也就是反斜杠本身需要转义。

Java预定义元字符

正则语法为我们提供了一些预定义的元字符,这些元字符规定了不同的匹配规则,它们不仅提供了强大的功能,而且大大简化了正则表达式规则的编写。

常见的预定义元字符如下表所示:

表:预定义的元字符
元字符 说明
. 匹配任意字符
\d 匹配0~9的所有数字
\D 匹配非数字
\s 匹配所有空白字符,包括空格、制表符、回车符、换页符、换行符等
\S 匹配所有非空白字符
\w 匹配字母、数字、下划线
\W 匹配所有非字母、非数字、非下划线

例如:
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("ABC".matches("A.C")); //true
        System.out.println("ABC".matches("A..")); //true
        System.out.println("@@".matches("..")); //true
        System.out.println("1-1".matches("\\d-\\d")); //true
        System.out.println("110".matches("\\d\\d\\d")); //true
        System.out.println("1A".matches("\\d\\D")); //true
        System.out.println("Ee".matches("\\D\\D")); //true
        System.out.println(" ".matches("\\s")); //true
        System.out.println("  ".matches("\\s")); //false
        System.out.println("FFF".matches("\\S\\S\\S")); //true
        System.out.println("G1 ".matches("\\S\\d\\s")); //true
        System.out.println("w_j".matches("\\w\\w\\w")); //true
        System.out.println("zzx".matches("zz\\w")); //true
        System.out.println("#1#".matches("\\W\\w\\W")); //true
        System.out.println("@#%".matches("\\W\\W\\W")); //true
    }
}

Java次数限定符

正则表达式还提供了次数限定符语法,它们用于描述匹配的次数范围。

常用的次数限定符如下表所示:

表:次数限定符
限定符 说明
* 匹配零次或零次以上
+ 匹配一次或一次以上
? 匹配零次或一次
{n} 匹配 n 次
{n,} 匹配 n 次或 n 次以上
{n,m} 匹配 n~m 次

例如:
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("ABC".matches("ABC\\d*")); //true
        System.out.println("ABC1".matches("ABC\\d*")); //true
        System.out.println("ABC123".matches("ABC\\d*")); //true
        System.out.println("ABC".matches("ABC\\d+")); //false
        System.out.println("ABC1".matches("ABC\\d+")); //true
        System.out.println("ABC123".matches("ABC\\d+")); //true
        System.out.println("ABC".matches("ABC\\d?")); //true
        System.out.println("ABC1".matches("ABC\\d?")); //true
        System.out.println("ABC123".matches("ABC\\d?")); //false
        System.out.println("ABC".matches("ABC\\d{3}")); //false
        System.out.println("ABC1".matches("ABC\\d{3}")); //false
        System.out.println("ABC123".matches("ABC\\d{3}")); //true
        System.out.println("ABC".matches("ABC\\d{3}")); //false
        System.out.println("ABC1".matches("ABC\\d{3}")); //false
        System.out.println("ABC123".matches("ABC\\d{3}")); //true
        System.out.println("ABC".matches("ABC\\d{2,}")); //false
        System.out.println("ABC12".matches("ABC\\d{2,}")); //true
        System.out.println("ABC123".matches("ABC\\d{2,}")); //true
        System.out.println("ABC".matches("ABC\\d{2,3}")); //false
        System.out.println("ABC12".matches("ABC\\d{2,3}")); //true
        System.out.println("ABC123".matches("ABC\\d{2,3}")); //true
    }
}

Java方括号表达式

相比于预定义元字符,方括号提供了一种更加灵活且全面的表达方式,方括号表达式用于匹配其中包含的任意字符,还能在方括号中使用“-”、“^”、“&&”以及方括号嵌套等实现更复杂的逻辑,详细说明如下表所示:

表:方括号的作用
方括号表达式 说明
[] 匹配方括号中包含的任意一个字符,比如 [ABC] 匹配 A、B 或 C
方括号中的- 描述匹配的字符范围,比如 [A-E] 匹配 A~E 的任意字符
方括号中的^ 表示非,比如 [^ABC] 匹配非 A、B、C 的任意字符
方括号中的&& 表示与,比如 [A-F&&ABC] 匹配 A、B 或 C 中的任意字符
方括号嵌套 表示并,比如 [ABC[XYZ]] 匹配 A、B、C、X、Y 或 Z 中的任意字符

可以通过下面实例帮助理解:
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("CC".matches("[ABC]{2}")); //true
        System.out.println("CEE".matches("[A-E]{2,}")); //true
        System.out.println("XXYZ".matches("[^ABC]+")); //true
        System.out.println("ABA".matches("[A-F&&ABC]{3}")); //true
        System.out.println("AACXZ".matches("[ABC[XYZ]]+")); //true
    }
}

Java开头符与结尾符

有时我们需要匹配开头与结尾的字符,此时就可以使用开头符(^)和结尾符($)。

观察下面的实例:
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("AA123".matches("^AA\\d{3}")); //true
        System.out.println("AA123".matches("AA\\d{3}")); //true
        System.out.println("AA123".matches("AA\\d{3}$")); //true
        System.out.println("AA123".replaceAll("^A", "B")); //BA123
        System.out.println("AA123".replaceAll("A", "B")); //BB123
        System.out.println("AA123".replaceAll("\\d$", "N")); //AA12N
        System.out.println("AA123".replaceAll("\\d", "N")); //AANNN
    }
}
可以发现前三个匹配结果都为 true,即在规则前后分别加 ^ 和 $ 与不加的匹配结果都一样。但如果调用 replaceAll() 方法进行正则替换时则匹配结果不同,很明显使用了 ^ 符号的只会匹配开头的那个字符并将其替换,使用了 $ 符号的只会匹配结尾的那个字符并将其替换,两者都不使用的情况下会替换所有匹配的字符。

Java或逻辑符拼接正则表达式

如果有两个或两个以上的正则表达式需要通过“或”逻辑拼接起来,那么可以使用或(|)逻辑符。

比如“AA|BB”能匹配“AA”或“BB”,再如“[0-5]{2}|[a-e]{3}”能匹配两个 0~5 的数字或三个 a~e 的字母。
public class RegexTest {
    public static void main(String[] args) {
        System.out.println("AA".matches("AA|BB")); //true
        System.out.println("BB".matches("AA|BB")); //true
        System.out.println("AB".matches("AA|BB")); //false
        System.out.println("02".matches("[0-5]{2}|[a-e]{3}")); //true
        System.out.println("020".matches("[0-5]{2}|[a-e]{3}")); //false
        System.out.println("abc".matches("[0-5]{2}|[a-e]{3}")); //true
        System.out.println("abcde".matches("[0-5]{2}|[a-e]{3}")); //false
    }
}

Java Pattern类

前面我们的正则表达式都是通过 String 类提供的 matches() 方法和 replaceAll() 方法来实现的,但 String 类并不能满足正则匹配的所有需求,此时就需要 JDK 为我们提供的更加强大的正则工具类,包括 Pattern 和 Matcher 两个核心类,它们位于 java.util.regex 包中。

Pattern 类用于创建一个正则表达式对象,所创建的对象表示对应的匹配规则。该类一般需要与另一个核心类 Matcher 一起使用才会发挥出强大的功能,不过 Pattern 类也能单独使用来实现一些简单的匹配操作。

Pattern 类的常用方法如下表所示:

表:Pattern 类的常用方法
方法 说明
static Pattern compile(String regex) 根据正则表达式创建 Pattern 对象
String pattern() 返回 Pattern 对象所对应的正则表达式
static boolean matches(String regex, CharSequence input) 根据指定的正则表达式来匹配字符串,若匹配成功,返回 true
String[] split(CharSequence input) 根据正则表达式规则对字符串进行分割
Matcher matcher(CharSequence input) 创建一个 Matcher 对象并指定要匹配的字符串

例如:
public class RegexTest {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("\\s");
        System.out.println(p.pattern());
        System.out.println(Pattern.matches("\\d*", "1231"));
        System.out.println(Pattern.matches("\\d*", "1231d"));
        String[] strs = p.split("123 342 243 32");
        for (String s : strs)
            System.out.println(s);
    }
}
输出结果为:

\s
true
false
123
342
243
32

仅有 Pattern 类的情况下只能实现简单的功能,比如某个字符串是否匹配某个正则表达式,或对某个字符串按正则表达式进行分割。

Java Matcher类

Matcher 类表示匹配器,它是一个正则匹配引擎,能通过 Pattern 对象进行更复杂的匹配功能,比如可以获取索引位置和分组匹配等。

Matcher类的常用方法如下表所示:

表:Matcher类的常用方法
方法名 说明
boolean matches() 判断表达式与字符串是否完整匹配
boolean find() 尝试在未匹配字符串中进行匹配
boolean find(int start) 从指定位置开始进行匹配
String group() 获取当前匹配的字符串
String group(int group) 获取分组匹配中指定索引的分组
int start() 返回匹配的字符串的起始索引
int start(int group) 返回分组匹配中指定索引的分组起始索引
int end() 返回匹配的字符串的结束位置加1
int end(int group) 返回分组匹配中指定索引的分组结束位置加1
boolean lookingAt() 判断表达式是否能从字符串开头匹配

下面实例展示了用 Pattern 类和 Matcher 类来实现正则匹配:
public class RegexTest {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("\\d+");
        Matcher m = p.matcher("22aa33");
        System.out.println(m.matches());
        Matcher m2 = p.matcher("2233");
        System.out.println(m2.matches() + "->[" + m2.start() + ":" + m2.end() + "]");
        Matcher m3 = p.matcher("aa2233");
        System.out.println(m3.lookingAt());
        Matcher m4 = p.matcher("22aa33");
        System.out.println(m4.lookingAt() + "->[" + m4.start() + ":" + m4.end() + "]");
        Matcher m5 = p.matcher("22bb33");
        System.out.println(m5.find() + "->[" + m5.start() + ":" + m5.end() + "]");
        System.out.println(m5.find() + "->[" + m5.start() + ":" + m5.end() + "]");
        Matcher m6 = p.matcher("aa2233");
        System.out.println(m6.find() + "->[" + m6.start() + ":" + m6.end() + "]");
        Matcher m7 = p.matcher("aabb");
        System.out.println(m7.find());
    }
}
输出结果为:

false
true->[0:4]
false
true->[0:2]
true->[0:2]
true->[4:6]
true->[2:6]
false

注意,matches()、find() 和 lookingAt() 这三个方法都能执行匹配操作,其中 matches() 方法必须完整匹配才为 true,lookingAt() 方法需要从开头匹配才为 true,find() 方法可以匹配未匹配过的子串,每调用一次 find() 方法就会接着往下匹配尚未匹配过的子串。

相关文章