在平时开发时,经常会有格式要求的判断,比如密码格式要求:
长度为6-16个字符
字母(不分大小写)或数字或特殊字符(*、$、@、!、#、?)至少包含其中2种
面对这样的一个判断要求,我们该如何实现呢?本文给出不使用正则和使用正则表达式两种解决方法,其中掌握正则的套路步骤可以较好的理解,去应用不同的场景。
1、非正则实现
思路
1、判断字符串是否为空
2、判断长度
3、定义三个变量,判断对应的字符是否找到
int alphabeticFound = 0; int digitalFound = 0; int specialCharFound = 0;
依次判断字符是否满足字母、数字或者特殊字符。如满足符合要求的字符,则相关变量赋值为1;如不符合则直接返回false,表示出现不符合要求的字符。
4、判断三个变量找到的情况,累加大于1,则表示至少满足2种字符
代码实现
import java.util.Arrays;import java.util.List;public class PasswordValidateExample { //特殊字符列表 private static final List<Character> SPECIAL_CHARS = Arrays.asList(‘_’,’*’,’@’,’!’,’#’,’%’,’?’,’$’); public static boolean isMatched1(String value) { if(value == null) { return false; } //检查长度 int len = value.length(); if(len <6 ||len >16) { return false; } int alphabeticFound = 0; int digitalFound = 0; int specialCharFound = 0; for(char c: value.toCharArray()) { if(Character.isAlphabetic(c)) { alphabeticFound = 1; }else if (Character.isDigit(c)) { digitalFound = 1; }else if(SPECIAL_CHARS.contains(c)) { specialCharFound = 1; } else { //不符合要求的字符 return false; } } return (alphabeticFound digitalFound specialCharFound) > 1; }}
代码测试
import java.util.Arrays;import java.util.List;import org.junit.Assert;import org.junit.Test;public class PasswordValidateExampleTest { // 不合规则的密码 private static final List<String> INVALID_PWDS = Arrays.asList(“123”, “abc”, “###”, “123456”, “abcABC”, “123ab”, “ab12%”, “123456789012345678”, “1234567890abcdefg”, “1234567890abcd*#$”); // 符合规则的密码 private static final List<String> VALID_PWDS = Arrays.asList(“123abc”, “abc@$%”, “12ab$a”, “123456#%”, “abcABC%”, “1234567890abc@@”, “1234567890abcAbc”, “1234567890abc%*@”); @Test public void testIsMatched1_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched1(invalidValue)); } } @Test public void testIsMatched1_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched1(validValue)); } }}
使用junit跑一下,执行通过
至此,一个简单的非正则判断就写好了。接下来我们来看一下使用正则如何去完成。
2、正则表达式实现
准备
在给出最终正则表达式之前,我们先尝试给出只要符合一种以上字符即可的场景。
纯数字的正则表达式
^[0-9]*$或者^[\\d]*$//限定长度6-16^[0-9]{6,16}$或者^[\\d]{6,16}$
纯字母的正则表达式
^[A-Za-z]*$//限定长度6-16^[A-Za-z]{6,16}$
纯特殊字符的正则表达式
^[_*@!#%?$]*$//限定长度6-16^[_*@!#%?$]{6,16}$
数字、字母、特殊字符只要满足一种的
^[0-9A-Za-z_*@!#%?$]*$或者^[\\dA-Za-z_*@!#%?$]*$//限定长度6-16^[0-9A-Za-z_*@!#%?$]{6,16}$或者^[\\dA-Za-z_*@!#%?$]{6,16}$
思路
既然最终的目标至少2种组合,那么,我们写正则表达式只要将只有一种情况的情况排除即可。也就是:
1、排除纯数字的情况2、排除纯字母的情况3、排除纯特殊字符的情况//使用排除的方法我们可以使用?!来完成
步骤
1、排除纯数字的情况
(?![0-9] $)或者(?![\\d] $)
2、排除纯字母的情况
(?![A-Za-z] $)
3、排除纯特殊字符的情况
(?![_*@!#%?$] $)
4、合法字符
有了步骤#1、#2和#3的条件,我们已经把只有一种的情况排除在外,那么符合条件的字符肯定包含2种及其2种以上的情况。合法字符如下:
[0-9A-Za-z_*@!#%?$]{6,16}或者[\\dA-Za-z_*@!#%?$]{6,16}
5、最终成型
最后我们只要把上述正则连接起来,并加上开始^和结束$标记即可。
^(?![0-9] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[0-9A-Za-z_*@!#%?$]{6,16}$或者^(?![\\d] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[\\dA-Za-z_*@!#%?$]{6,16}$
程序实现
补充isMatched2和isMatched3方法。
import java.util.Arrays;import java.util.List;public class PasswordValidateExample { //特殊字符列表 private static final List<Character> SPECIAL_CHARS = Arrays.asList(‘_’,’*’,’@’,’!’,’#’,’%’,’?’,’$’); private static final String REGEX_1 = “^(?![0-9] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[0-9A-Za-z_*@!#%?$]{6,16}$”; //与REGEX_1等价,只是用\\d来替换了0-9 private static final String REGEX_2 = “^(?![\\d] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[\\dA-Za-z_*@!#%?$]{6,16}$”; public static boolean isMatched2(String value) { return isMatched(value,REGEX_1 ); } public static boolean isMatched3(String value) { return isMatched(value,REGEX_2 ); } public static boolean isMatched(String value, String regex) { return value == null ? false : value.matches(regex); } public static boolean isMatched1(String value) { if(value == null) { return false; } //检查长度 int len = value.length(); if(len <6 ||len >16) { return false; } int alphabeticFound = 0; int digitalFound = 0; int specialCharFound = 0; for(char c: value.toCharArray()) { if(Character.isAlphabetic(c)) { alphabeticFound = 1; }else if (Character.isDigit(c)) { digitalFound = 1; }else if(SPECIAL_CHARS.contains(c)) { specialCharFound = 1; } else { //不符合要求的字符 return false; } } return (alphabeticFound digitalFound specialCharFound) > 1; }}
代码测试
补充isMatched2和isMatched3的测试方法。
import java.util.Arrays;import java.util.List;import org.junit.Assert;import org.junit.Test;public class PasswordValidateExampleTest { // 不合规则的密码 private static final List<String> INVALID_PWDS = Arrays.asList(“123”, “abc”, “###”, “123456”, “abcABC”, “123ab”, “ab12%”, “123456789012345678”, “1234567890abcdefg”, “1234567890abcd*#$”); // 符合规则的密码 private static final List<String> VALID_PWDS = Arrays.asList(“123abc”, “abc@$%”, “12ab$a”, “123456#%”, “abcABC%”, “1234567890abc@@”, “1234567890abcAbc”, “1234567890abc%*@”); @Test public void testIsMatched1_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched1(invalidValue)); } } @Test public void testIsMatched1_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched1(validValue)); } } @Test public void testIsMatched2_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched2(invalidValue)); } } @Test public void testIsMatched2_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched2(validValue)); } } @Test public void testIsMatched3_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched3(invalidValue)); } } @Test public void testIsMatched3_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched3(validValue)); } }}
使用junit跑一下,执行通过
至此,开头提到的密码格式要求的判断就完成了。
密码格式要求:
长度为6-16个字符
字母(不分大小写)或数字或特殊字符(*、$、@、!、#、?)至少包含其中2种
3、套路回顾和扩展
套路步骤回顾
针对多种字符组合的判断,就是按照排除法的套路出牌,3个步骤即可。
先写出不符合的情况,拼接在一起再写出合法字符的情况最后加个开始^和结束$标记串联起来即可
1、排除纯数字的情况
(?![0-9] $)或者(?![\\d] $)
2、排除纯字母的情况
(?![A-Za-z] $)
3、排除纯特殊字符的情况
(?![_*@!#%?$] $)
4、合法字符
有了步骤#1、#2和#3的条件,我们已经把只有一种的情况排除在外,那么符合条件的字符肯定包含2种及其2种以上的情况。合法字符如下:
[0-9A-Za-z_*@!#%?$]{6,16}或者[\\dA-Za-z_*@!#%?$]{6,16}
5、最终成型
最后我们只要把上述正则连接起来,并加上开始^和结束$标记即可。
^(?![0-9] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[0-9A-Za-z_*@!#%?$]{6,16}$或者^(?![\\d] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[\\dA-Za-z_*@!#%?$]{6,16}$
写法变形
针对多个(?!xxxx)(?!xxxx),我们也可以使用符号|连接做一个变形,如:
^(?!^([0-9] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$或者^(?!^([\\d] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$
同样,我们补充方法isMatched4和isMatched5,并补充测试方法如下:
//变形 private static final String REGEX_3 = “^(?!^([0-9] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$”; private static final String REGEX_4 = “^(?!^([\\d] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$”; public static boolean isMatched4(String value) { return isMatched(value,REGEX_3 ); } public static boolean isMatched5(String value) { return isMatched(value,REGEX_4 ); }
测试一下
@Test public void testIsMatched4_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched4(invalidValue)); } } @Test public void testIsMatched4_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched4(validValue)); } } @Test public void testIsMatched5_invalid() { for (String invalidValue : INVALID_PWDS) { Assert.assertFalse(PasswordValidateExample.isMatched5(invalidValue)); } } @Test public void testIsMatched5_valid() { for (String validValue : VALID_PWDS) { Assert.assertTrue(PasswordValidateExample.isMatched5(validValue)); } }
同样case执行成功
扩展
所以,针对上述的需求,我们的正则表达式可以写如下几种:
^(?![0-9] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[0-9A-Za-z_*@!#%?$]{6,16}$或者^(?![\\d] $)(?![A-Za-z] $)(?![_*@!#%?$] $)[\\dA-Za-z_*@!#%?$]{6,16}$或者^(?!^([0-9] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$或者^(?!^([\\d] |[A-Za-z] |[_*@!#%?$] )$)[0-9A-Za-z_*@!#%?$]{6,16}$
通过前面的说明,想必大家对这种条件的正则已经有较好的理解了。
最后再来套用步骤练习一下,
密码格式要求调整下:
长度为6-16个字符
字母(不分大小写)或数字或特殊字符(*、$、@、!、#、?)3种都要包含
请先思考一下,再看答案
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
一个参考答案的步骤如下:
1、排除纯只包含数字字母的情况
(?![0-9A-Za-z] $)
2、排除纯只包含数字特殊字符的情况
(?![0-9_*@!#%?$] $)
3、排除只包含字母特殊字符的情况
(?![A-Za-z_*@!#%?$] $)
4、合法字符
有了步骤#1、#2和#3的条件,我们已经将如下几种情况排除
只包含数字
只包含字母
只包含特殊字符
只包含数字和字母
只包含数字和特殊字符
只包含字母和特殊字符
剩下只要写上合法字符情况,有了前面的排除条件,其必然都包含数字、字母和特殊字符。
[0-9A-Za-z_*@!#%?$]{6,16} 或者 [\\dA-Za-z_*@!#%?$]{6,16}
5、最终成型
最后我们只要把上述正则连接起来,并加上开始^和结束$标记即可。
^(?![0-9A-Za-z] $)(?![0-9_*@!#%?$] $)(?![A-Za-z_*@!#%?$] $)[0-9A-Za-z_*@!#%?$]{6,16}$或者[\\dA-Za-z_*@!#%?$]{6,16}
你学会了吗?
当然,还可以增加难度,比如支持中文;至少2种、3种或者全包含。有兴趣的读者可以按照上述套路尝试一下。有套路,不怕变形。