正则表达式(四):Java regex
以下示例所使用 Java 版本为: 1.8.0
有了上一章 python 中的 re 模块的铺垫(正则表达式(三):python re模块),对于 Java 中正则的使用理解上会简单许多。
Java 作为一种被广泛使用的编程语言,从 jdk-1.4 开始,标准库提供了 java.util.regex 包来支持正则表达式的使用。正则在 Java 中的使用和 python 中略有区别,主要是使用方式上稍有差异。名称上的不同足可见一斑,python 中两个核心对象是 Pattern 和 Match ,而 Java 中则是 Pattern 和 Matcher。
核心对象作用
1. re 模块
re 模块中 Pattern 对象作为匹配规则,代表了一种文本模式,Pattern 对象基于该模式提供了多种对指定内容的处理操作,如:match、split、sub等; Match 对象作为模式和指定内容的分组处理结果,提供了多种对数据信息的提取方式,如:group、groups、span等。根据 re 模块中两种对象的使用特点,可以有如下归纳。
1. Pattern 对象作用主要是:提供模式对指定内容的多种处理方式,可以返回 Match 对象进一步提取处理结果或者直接返回处理结果;
2. Match 对象作为模式的分组处理后对象,其体现作用主要是:对处理结果信息的提取。
2. regex 包
Java 标准库中 regex 包提供的正则功能同样依赖于两个核心对象,名称上与 re 模块核心对象相似,使用方式上也很相似。regex 包中 Pattern 对象作为一种匹配规则,一种文本模式,提供了直接返回结果的函数,如:matches、split等,这些函数直接返回模式处理后的结果。但是涉及分组的操作,则全部由 Matcher 对象提供,Pattern 对象提供有 match 函数来构造 Matcher 对象。Matcher 对象则提供对分组的处理和结果提取函数,如:find、group、start、end等。除此之外,Matcher 对象还提供有 reset、replaceAll、lookingAt等函数,比较 regex 包和 re 模块对正则处理的使用方式,可以发现一个现象:
相同点:两者的 Pattern 对象都提供了正则模式对指定内容的直接处理,对分组结果的提取操作同样都放在 Match | Matcher 对象中完成。
不同点:re 模块中 Match 对象提供的功能更聚焦于对分组结果的操作,对于split、sub等直接返回结果的、非分组相关的函数放在了 Pattern 对象中提供;而 regex 包中 Pattern 对象和 Matcher 对象都提供有非分组相关的函数,如 Pattern 对象中的 split 函数,Matcher 对象中的 replaceAll 函数。
下面列出 Pattern 对象中常用函数:
函数名 | 作用 |
---|---|
compile(String regex) | 返回根据指定正则表达式生成 Pattern 模式对象 |
compile(String regex, int flags) | 返回根据指定正则表达式和匹配标志生成的 Pattern 模式对象 |
matches(String regex, CharSequence input)) | 判断正则表达式是否匹配指定内容并返回 |
quote(String s) | 转换指定内容为字面值并返回 |
matcher(CharSequence input) | 返回 Pattern 对象和指定内容构成的 Matcher 对象 |
split(CharSequence input) | 对指定内容进行分割,返回分割得到的结果列表 |
split(CharSequence input, int limit) | 对指定内容按照指定次数进行分割,返回分割得到的结果列表 |
Pattern 的私有构造函数
在开始介绍具体的使用之前,首先引入一点,在 Pattern 类中使用了私有的构造函数,提供了 compile 静态函数完成对象的构造。声明私有构造函数的场景很多,原因主要是在构造对象之前或者之后作一些额外的操作:
1. 实用程序类,不需要实例化,如java.lang.Math类
2. 构造函数可能会失败,不能返回 null
3. 控制实例化对象个数,如单例的使用
4. 静态函数更能阐明实例化过程的意义,或后续会添加其他操作到静态函数中
在 Pattern 类的场景中,其声明私有构造函数,使用静态函数完成实例化的原因偏向于第四种。Pattern 的构造需要一些复杂的操作,通过 new Pattern( .. ) 的形式构造一个新模式显得意义不大,因为正则表达式是通过编译后成为一种模式使用的,所以使用静态函数 compile 来解释这个过程。
--- What is the use of making constructor private in a class?
--- Java Pattern class doesn't have a public constructor, why?
- compile、matches、quote 函数
这三类为 Pattern 类提供的静态函数,因为 Pattern 类构造函数是私有的,所以提供了 compile 函数来生成 Pattern 对象;matches 函数直接返回正则表达式与指定内容是否匹配的结果,其内部实现流程仍然是先调用 compile 函数构造 Pattern 对象,然后调用 match 函数构造 Matcher 对象,最后使用 Matcher 对象的 matches 函数返回是否匹配结果;quote 函数则返回正则表达式的字面值内容。
示例:
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg1 = "[\\w]+";
String str1 = "1a2b3c";
Pattern pattern = Pattern.compile(reg1);
System.out.println("reg1 pattern is "+pattern.toString());
System.out.println("str1 matches is "+Pattern.matches(reg1,str1));
String reg2 = Pattern.quote(reg1);
String str2 = reg1;
System.out.println("str2 matches is "+Pattern.matches(reg2,str2));
}
}
运行结果:
reg1 pattern is [\w]+
str1 matches is true
str2 matches is true
由以上示例可知,quote 函数返回的正则表达式的字面值,也就是其普通意义字符,所以可以作为普通字符匹配其本身字符。
- matcher 函数
Pattern 类中 matcher 函数的作用就是构造一个 Matcher 对象并返回
示例:
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg1 = "[\\w]+";
Pattern pattern1 = Pattern.compile(reg1);
Pattern pattern2 = Pattern.compile(reg1);
System.out.println(pattern1 == pattern2);
}
}
运行结果:
false
从示例中可以看出,相对于 python 中 re 模块的缓存实现,Java 的 regex 包中关于正则模式的构建并没有实现缓存的功能,在后续的 compile 静态函数可能会增加该实现。
- split 函数
Pattern 类中 split 函数提供对指定内容进行分割的功能,返回分割得到的字符串数组。函数中包含一个可选的 limit 参数,用于指定分割后数组元素个数的上限,默认值 0 表示全分割。
示例:
import java.util.Arrays;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "\\d";
String str = "1a2b3c";
Pattern pattern1 = Pattern.compile(reg);
String[] arr1 = pattern1.split(str);
String[] arr2 = pattern1.split(str,2);
System.out.println("arr1 = "+Arrays.toString(arr1));
System.out.println("arr2 = "+Arrays.toString(arr2));
}
}
运行结果:
arr1 = [, a, b, c]
arr2 = [, a2b3c]
下面列出 Matcher 对象中常用函数:
函数名 | 作用 |
---|---|
matches() | 判断正则表达式是否匹配指定内容并返回 |
find() | 从起点或上一个匹配位置后开始查找是否存在下一个匹配内容并返回 |
find(int start) | 重新从指定位置或上一个匹配位置后开始查找是否存在下一个匹配内容并返回 |
group() | 返回匹配内容 |
group(int group) | 返回指定序号分组匹配内容 |
group(String name) | 返回指定名称分组匹配内容 |
lookingAt() | 重新判断正则是否与给定内容的起始部分匹配并返回 |
replaceAll(String replacement) | 以指定内容替换全部匹配内容并返回替换后结果 |
replaceFirst(String replacement) | 以指定内容替换第一项匹配内容并返回替换后结果 |
start() | 返回匹配内容的首下标 |
start(int group) | 返回指定序号分组匹配内容的首下标 |
start(String name) | 返回指定名称分组匹配内容的首下标 |
end() | 返回匹配内容的尾下标 |
end(int group) | 返回指定序号分组匹配内容的尾下标 |
end(String name) | 返回指定名称分组匹配内容的尾下标 |
- matches 函数
Matcher 对象提供的 matches 函数作用同 Pattern 的静态函数 matches 一样,判断正则是否完全匹配内容,并返回判断结果。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "[\\w]+";
String str1 = "5b3ac";
String str2 = "5b3ac=";
Pattern pattern = Pattern.compile(reg);
Matcher matcher1 = pattern.matcher(str1);
Matcher matcher2 = pattern.matcher(str2);
System.out.println("match1 matches = "+matcher1.matches());
System.out.println("match2 matches = "+matcher2.matches());
}
}
运行结果:
match1 matches = true
match2 matches = false
- find 函数
find 函数提供有一个指定开始位置的参数,默认从起始位置或上一个匹配位置后开始查找是否存在下一个匹配内容,如果通过参数指定起始位置,则重新从该指定位置开始查找是否存在下一个匹配内容。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "\\d";
String str = "a1b2c3";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(str);
System.out.println("round one----");
System.out.println("first found = "+matcher.find());
System.out.println("second found = "+matcher.find());
System.out.println("third found = "+matcher.find());
System.out.println("fourth found = "+matcher.find());
System.out.println("\nround two----");
System.out.println("first found = "+matcher.find(2)); // "b" position
System.out.println("second found = "+matcher.find());
System.out.println("third found = "+matcher.find());
System.out.println("fourth found = "+matcher.find());
}
}
运行结果:
round one----
first found = true
second found = true
third found = true
fourth found = false
round two----
first found = true
second found = true
third found = false
fourth found = false
由示例可知,find 函数会根据匹配规则,在给定的内容中从前向后进行匹配查询。当指定 find 函数的起始位置后,会重新从指定的位置开始进行查询。
- group 函数
group 函数有三种形式:
1. 当无参数时返回整个正则表达式匹配内容
2. 当指定分组序号时,返回指定序号分组匹配的内容
3. 当指定分组的名称时,返回指定名称分组匹配的内容
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "\\d(\\d)(?<id>\\d)";
String str = "123abc456";
Pattern pattern = Pattern.compile(reg);
Matcher matcher = pattern.matcher(str);
System.out.println("matcher matches = "+matcher.find());
System.out.println("no parameter = "+matcher.group());
System.out.println("number parameter = "+matcher.group(1));
System.out.println("name parameter = "+matcher.group("id"));
}
}
运行结果:
matcher matches = true
no parameter = 123
number parameter = 2
name parameter = 3
- lookingAt 函数
lookingAt 函数有两个功能:
1. 判断正则表达式是否匹配给定内容的起始部分
2. 如果正则匹配内容的起始部分,则重新从内容的起始部分开始查询匹配内容,相当于执行了 find(0) 函数
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "\\d";
Pattern pattern = Pattern.compile(reg);
System.out.println("round one----");
String str = "1a2b";
Matcher matcher = pattern.matcher(str);
System.out.println("first found = "+matcher.find()); //true
System.out.println("second found = "+matcher.find());//true
System.out.println("third found = "+matcher.find()); //false
System.out.println("lookingAt found = "+matcher.lookingAt()); // reg 匹配 str 起始部分 //true
System.out.println("second found = "+matcher.find());//true
System.out.println("third found = "+matcher.find());//false
System.out.println("\nround two----");
String str2 = "a1b2";
Matcher matcher2 = pattern.matcher(str2);
System.out.println("first found = "+matcher2.find());//true
System.out.println("second found = "+matcher2.find());//true
System.out.println("third found = "+matcher2.find());//false
System.out.println("lookingAt found = "+matcher2.lookingAt()); // reg 不匹配 str 起始部分 //false
System.out.println("second found = "+matcher2.find());//false
System.out.println("third found = "+matcher2.find());//false
}
}
运行结果:
round one----
first found = true
second found = true
third found = false
lookingAt found = true
second found = true
third found = false
round two----
first found = true
second found = true
third found = false
lookingAt found = false
second found = false
third found = false
由示例可知,lookingAt 函数类似于 String 对象的 "startsWith" 函数,判断正则是否匹配给定内容的起始部分,除此之外,若匹配起始部分成功,则相当于执行了一次 find(0) 函数,设置查询分组的起始位置为第一个匹配结果后。
- replaceAll、replaceFirst 函数
这两个实现 replace 功能的函数使用很相似,replaceAll 替换内容中所有符合正则模式的部分,replaceFirst 则替换内容中第一个符合正则模式的部分。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "(\\d)\\d";
Pattern pattern = Pattern.compile(reg);
String str = "a12b34";
Matcher matcher = pattern.matcher(str);
System.out.println("replaceAll = "+matcher.replaceAll("+"));
System.out.println("replaceFirst = "+matcher.replaceFirst("+"));
}
}
运行结果:
replaceAll = a+b+
replaceFirst = a+b34
该示例展示了 Matcher 对象提供的与分组无关的替换指定内容功能。
- start、end 函数
这两个函数都是与分组相关的函数,start 函数返回指定分组或整个正则表达式匹配部分的首下标,end 函数返回指定分组或整个正则表达式匹配部分的尾下标(左闭右开)。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "(?<number1>\\d)(?<number2>\\d)\\d";
Pattern pattern = Pattern.compile(reg);
String str = "a123b456";
Matcher matcher = pattern.matcher(str);
if(matcher.find())
{
System.out.println("regex matched = "+matcher.group());
System.out.println("number1 matched(using No) = "+matcher.group(1));
System.out.println("number2 matched(using name) = "+matcher.group("number2"));
System.out.println("\nregex start position = "+matcher.start());
System.out.println("number1 start(using No) position = "+matcher.start(1));
System.out.println("number2 start(using name) position = "+matcher.start("number2"));
System.out.println("\nregex end position = "+matcher.end());
System.out.println("number1 end(using No) position = "+matcher.end(1));
System.out.println("number2 end(using name) position = "+matcher.end("number2"));
}
}
}
运行结果:
regex matched = 123
number1 matched(using No) = 1
number2 matched(using name) = 2
regex start position = 1
number1 start(using No) position = 1
number2 start(using name) position = 2
regex end position = 4
number1 end(using No) position = 2
number2 end(using name) position = 3
由示例可知,group、start、end 函数都提供有三种形式的函数,来获取分组匹配的内容信息。无参时面向的是整个正则表达式匹配结果;参数为分组序号时,面向的是指定序号分组匹配的部分;参数为分组名称时,面向的是指定名称分组匹配的部分。
针对 group、start、end 这些获取分组匹配信息的函数,在这里介绍可能出现的三种异常:
IllegalStateException:获取分组匹配的内容相关信息之前,没有对正则模式执行匹配操作,或匹配操作失败了,则提示状态异常;
IndexOutOfBoundsException:不存在指定分组序号匹配的结果时,提示序号越界异常;
IllegalArgumentException:不存在指定分组名称匹配的结果时,提示参数异常。
示例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class t2
{
public static void main(String[] args)
{
String reg = "(?<number1>\\d)(?<number2>\\d)\\d";
Pattern pattern = Pattern.compile(reg);
String str = "a123b456";
Matcher matcher = pattern.matcher(str);
System.out.println("IllegalStateException example = ");
try
{
matcher.group();
}
catch (IllegalStateException e)
{
System.out.println(e.toString());
}
System.out.println("\nIndexOutOfBoundsException example = ");
try
{
matcher.find();
matcher.group(-1);
}
catch (IndexOutOfBoundsException e)
{
System.out.println(e.toString());
}
System.out.println("\nIllegalArgumentException example = ");
try
{
matcher.group("number3"); //上一步已经执行过matcher.find()
}
catch (IllegalArgumentException e)
{
System.out.println(e.toString());
}
}
}
运行结果:
IllegalStateException example =
java.lang.IllegalStateException: No match found
IndexOutOfBoundsException example =
java.lang.IndexOutOfBoundsException: No group -1
IllegalArgumentException example =
java.lang.IllegalArgumentException: No group with name <number3>
由以上示例可以大致了解 Java 标准库 regex 包中对正则表达式的常用使用方式,与 re 模块中正则的使用方式略有差异,但区别不大。熟悉任何一种语言中的正则使用方式,对于别的语言中正则的应用就会变得很容易理解,因为正则本身体现的是一种文本处理规则,核心价值是其强大的模式匹配能力,并非各种花式操作,不同的语言只是对其提供不同的应用平台,作为一种“工具”,其具备通用性。