5.3.2 利用正则表达式校验字符串
5.3.1小节多次提到了正则串和正则表达式,那么正则表达式究竟是符合什么定义的字符串呢?正则表达式是编程语言处理字符串格式的一种逻辑式子,它利用若干保留字符定义了形形色色的匹配规则,从而通过一个式子来覆盖满足上述规则的所有字符串。正则表达式的保留字符主要有:圆括号、方括号、花括号、竖线、横线、点号、加号、星号、反斜杆等,这些保留字符的作用详见5.3.1小节,它们的用途总结见表5-2。
表5-2 字符串模板的格式转换符
正则表达式除了用在split方法中切割字符串外,还可以用在matches方法中判断字符串是否符合正则条件。以手机号码为例,无论是移动、联通还是电信的手机号,统统都是11位数字,并且第一位数字固定为1,第二位数字可能是3、4、5、7、8,再加上9位数字凑成11位手机号。那么通过正则表达式书写11位手机号码的规则,第一位就用“1”表示,第二位可用“[34578]”表示,后面的9位数字使用“\\d{9}”表示,整合起来便形成了最终的手机号码正则串“1[34578]\\d{9}”。下面是使用isPhone方法根据这个正则表达式校验手机号码的例子(完整代码见本章源码的src\com\string\regular\RegexMatch.java):
// 利用正则表达式检查字符串是否为合法的手机号码 public static boolean isPhone(String phone) { // 开头的“1”代表第一位为数字1,“[3-9]”代表第二位可以为3~9的某个数字 // “\\d{9}”代表后面是9位数字 String regex="1[3-9]\\d{9}"; // 字符串变量的matches方法返回正则表达式对该串的检验结果 // 返回true表示符合字符串规则,返回false表示不符合规则 return phone.matches(regex); }
再来一个更复杂的字符串校验——身份证号码的格式校验。中国的二代身份证号码共有18位,其中前6位是地区编码,中间8位是公民的出生年月日,后面3位是该地区当日的出生序号,最后一位是校验码。身份证的前6位地区编码可通过正则表达式“\\d{6}”校验,中间的8位出生年月日可再拆分为4位的年份、两位的月份和两位的日期。一个健在公民的出生年份只可能是20世纪和21世纪的某一年,也就是说,4位年份必定以19或者20开头,因此正则串“(19|20)\\d{2}”即可覆盖这两个世纪的200个年份。此时校验年份的正则方法代码如下:
// 校验4位的年份字符串 public static void checkYear() { String regex="(19|20)\\d{2}"; // 4位年份数字必须以19或者20开头 for (int i=1899; i<=2100; i++) { if (i>1910 && i<2090) { // 缩小待校验年份的区间范围 continue; } String year=i+""; boolean check=year.matches(regex); // 校验该年份是否匹配正则表达式的规则 System.out.println("year="+year+", check="+check); } }
年份校验完毕,后面的月份更简单,因为两位月份就是01~12中间的12个数字。如果月份首位是0,那么第二位可以是1~9;如果月份首位是1,那么第二位只能是0~2。据此可把月份的正则表达式分解成两个关系为“或”的子表达式,其中第一个表达式可使用“0[1-9]”,第二个表达式可使用“1[0-2]”,两个表达式通过竖线连接起来便形成了完整的月份表达式“0[1-9]|1[0-2]”。此时校验月份的正则方法代码如下:
// 校验两位的月份字符串 public static void checkMonth() { String regex="0[1-9]|1[0-2]"; // 月份的校验规则,合法的月份数字从01~12 for (int i=0; i<=13; i++) { String month=String.format("%02d", i); boolean check=month.matches(regex); // 校验该月份是否匹配正则表达式的规则 System.out.println("month="+month+", check="+check); } }
月份后面的日期校验起来稍微有些复杂。合法的两位日期可以是01~31中间的31个数字,故而日期的正则校验需要分解成以下3种情况:
(1)日期首位是0,那么第二位可以是1~9,这种情况的正则表达式应为“0[1-9]”。
(2)日期首位是1或者2,那么第二位可以是0~9,这种情况的正则表达式应为“[12]\\d”。
(3)日期首位是3,那么第二位只能是0~1,这种情况的正则表达式应为“3[01]”。
综合以上3种情况,得到完整的日期校验正则串为“0[1-9]|[12]\\d|3[01]”。此时校验日期的正则方法代码如下:
// 校验两位的日期字符串 public static void checkDay() { String regex="0[1-9]|[12]\\d|3[01]"; // 日期的校验规则 for (int i=0; i<=32; i++) { String day=String.format("%02d", i); boolean check=day.matches(regex); // 校验该日期是否匹配正则表达式的规则 System.out.println("day="+day+", check="+check); } }
然后还要校验身份证号码的末尾4位,包括3位出生编码和一位校验码。其中出生编码为3位数字,而校验码除了数字以外还可能是小写的x或者大写的X,因此出生编码和校验码也得分别加以判断。3位的出生编码对应的正则表达式为“\\d{3}”,一位的校验码对应的正则表达式为 “[0-9xX]”,二者的式子合起来就变成了“\\d{3}([0-9xX])”。下面的代码可生成4位的字符串,并进行身份证末4位的正则校验:
// 校验身份证号码末尾的4位编号串 public static void checkLastFour() { String regex="\\d{3}([0-9xX])"; // 身份证末尾4位的校验规则 for (int i=0; i < 36; i++) { // 循环生成多个待校验的4位字符串 char last; if (i < 10) { // 小于10的时候,取数字符号 last=(char) ('0' + i); // 转换成数字字符 } else { // 大等于10的时候,取字母符号 last=(char) ('A' + i - 10); // 转换成字母字符 } String lastFour=String.format("%03d%c", i * 13, last); boolean check=lastFour.matches(regex); //校验该字符串是否是合法的身份证末4位 System.out.println("lastFour=" + lastFour + ", check=" + check); } }
以上把18位身份证号码的各个区间分别做了正则校验,最后还要组装各区间的正则表达式。这时,为了避免各区间的表达式互相干扰,可以利用圆括号将各区间的作用范围先行界定,就像这样:“(6位地区编码)(4位年份)(两位月份)(两位日期)(末尾4位编号)”,接着再把各区间的正则表达式分别填入该区间的圆括号中,便形成了最终的身份证号码正则串。包含正则串在内的身份证校验的完整方法如下:
// 利用正则表达式检查字符串是否为合法的身份证号码 public static boolean isICNO(String icno) { //String regex="(6位地区编码)(4位年份)(两位月份)(两位日期)(末尾4位编号)"; String regex="(\\d{6})((19|20)\\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01]) (\\d{3}([0-9xX]))"; return icno.matches(regex); }