好好学Java:从零基础到项目实战
上QQ阅读APP看书,第一时间看更新

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);
    }