正则表达式-异常
D:\code\黑马\code\expression\src\com\lmh
正则表达式
由特定的字符组成,代表某种规则,可以简化代码
书写规则
- String提供了一个方法用于判断是否匹配正则表达式
- public boolean matches(String regex) 参数为正则表达式
1. 字符类(只匹配单个字符)
符号 | 含义 |
---|---|
[ ] | 里面的内容只出现一次,只能是[ ]中的内容之一 |
^ | 取反 |
- | 从哪-哪 |
&& | 交集 |
| | 并集,不写默认为并集 |
例子
//带[]的只能匹配单个字符,如果超过一个字符都是false
public class Test3 {
public static void main(String[] args) {
System.out.println("a".matches("[abc]"));// [abc]只能匹配a、b、c,所以返回true
//等价于
System.out.println("a".matches("[a-c]"));// [a-c]只能匹配a、b、c,所以返回true
//等价于
System.out.println("a".matches("[a|b|c]"));
// [a|b|c]只能匹配a、b、c,所以返回true,默认|可以省略
System.out.println("e".matches("[abcd]"));
// [abcd]只能匹配a、b、c、d, 不能是e,所以返回false
"============================================================================="
//^只要出现,就表示后面的整体为非,即不能是后面的整体
System.out.println("d".matches("[^abc]"));
// [^abc]表示不能是abc,可以是其他任意字符,所以返回true
//等价于
System.out.println("d".matches("[^a-c]"));
// [^a-c]表示不能是a-c,可以是其他任意字符,所以返回true
//等价于
System.out.println("d".matches("[^a|b|c]"));
// [^a|b|c]表示不能是a|b|c,可以是其他任意字符,所以返回true
System.out.println("a".matches("[^abc]")); // false
"============================================================================="
System.out.println("b".matches("[a-zA-Z]"));
// [a-zA-Z]表示只能是a-z A-Z的字符,所以返回true
//等价于
System.out.println("b".matches("[a-z]|[A-Z]")); //true
//等价于
System.out.println("b".matches("[a-z[A-Z]]")); // true
System.out.println("2".matches("[a-zA-Z]")); // false
"============================================================================="
System.out.println("k".matches("[a-z&&[^bc]]")); //a到z,除了b和c,所以返回true
//等价于
System.out.println("b".matches("[a[d-z]]")); //false
System.out.println("b".matches("[a-z&&[^bc]]")); // false
System.out.println("ab".matches("[a-zA-Z0-9]"));
//表示只能是a-z A-Z 0-9的单个字符,两个字符返回false
}
}
2. 预定义字符(只匹配单个字符)
符号 | 含义 |
---|---|
. | 表示除\n外的任意字符 |
\ | 表示转义 |
\d | 表示一个[0-9]之间的数字,即数字字符 |
\D | 表示一个非数字[^0-9] 的字符 |
\s | 表示空白字符 |
\S | 表示非空白字符 |
\w | 表示数字、字母或下划线:[a-zA-Z_0-9] |
\W | 表示非数字、字母和下划线 |
注意:下面的代码中的\\w就表示\w,因为\ \w中的第一个\是转义字符,告诉计算机\\w就是\w,否则如果直接输入\w,计算机会默认将第一个\理解为转义,能看到的也就只有w
//只能匹配单个字符,如果超过一个字符都是false
public class Test4 {
public static void main(String[] args) {
//.代表除了换行符以外的任意字符
System.out.println("徐".matches(".")); // .可以匹配任意字符,所以返回true
System.out.println("\n".matches(".")); // 不能匹配换行符,所以返回false
System.out.println("徐徐".matches(".")); // 因为是两个字符,所以为false
System.out.println("==============================================");
// \d: 表示一个数字字符,等价于[0-9],因为\为转义字符,所以\d要写成\\d
System.out.println("3".matches("\\d")); // \d: 0-9的数字,所以返回true
System.out.println("a".matches("\\d")); //false
// \D: 表示一个非数字字符,等价于[^0-9]
System.out.println("a".matches("\\D")); // \D: 不能是0-9的数字,所以返回true
System.out.println("3".matches("\\D")); // false
System.out.println("==============================================");
// \s: 代表一个空白字符,包括空格、制表符、换页符、换行符
System.out.println(" ".matches("\\s")); //true
System.out.println("\t".matches("\\s")); //true
System.out.println("\n".matches("\\s")); //true
System.out.println("a".matches("\\s")); // false
// \S: 代表一个非空白字符,除了空格、制表符、换页符、换行符
System.out.println("a".matches("\\S")); // true
System.out.println(" ".matches("\\S")); // false
System.out.println("==============================================");
// \w: 代表一个数字、字母、下划线字符,等价于[a-zA-Z_0-9]
System.out.println("a".matches("\\w")); // true
System.out.println("_".matches("\\w")); // true
System.out.println("徐".matches("\\w")); // false
System.out.println("__".matches("\\w"));// false,因为只能匹配单个字符
// \W: 代表一个非数字、字母、下划线字符,等价于[^a-zA-Z_0-9]
System.out.println("徐".matches("\\W")); // true
System.out.println("a".matches("\\W")); // false
}
}
3. 数量词(匹配多个字符)
下表中的X代表单个字符,即上文的字符类或预定义字符
符号 | 含义 |
---|---|
X ? | X出现0次或一次,并且不能出现X以外的字符 |
X * | X出现0次或多次,并且不能出现X以外的字符 |
X + | X出现1次或多次,并且不能出现X以外的字符 |
X {n} | X正好出现n次,并且不能出现X以外的字符 |
X {n, } | X至少出现n次,并且不能出现X以外的字符 |
X{n, m} | X至少出现n次但是不能超过m次,并且不能出现X以外的字符 |
注意:数量词中只能出现指定字符,也就是说如果X为666,那么出现66也是错的,因为不是666,详见下文中的小案例的需求二
public class Test2 {
public static void main(String[] args) {
//?表示出现0次或1次
System.out.println("a".matches("\\w?")); //表示\w出现0次或1次,所以返回true
System.out.println("".matches("\\w?")); // 出现0次也可以,所以返回true
System.out.println("abc".matches("\\w?")); // false,因为出现了3次
System.out.println("====================================");
// * 代表0次或多次
System.out.println("abc12".matches("\\w*")); //出现5次,所以返回true
System.out.println("".matches("\\w*")); // 出现0次也可以,所以返回true
System.out.println("abc12张".matches("\\w*")); //出现了张,所以返回false
System.out.println("====================================");
// + 代表1次或多次
System.out.println("abc12".matches("\\w+")); //出现5次,所以返回true
System.out.println("".matches("\\w+")); // 出现0次不可以,所以返回false
System.out.println("abc12张".matches("\\w+")); // 出现了张,所以返回false
System.out.println("====================================");
// {n} 代表正好出现n次
System.out.println("a3c".matches("\\w{3}")); //出现了3次,所以返回true
System.out.println("abcd".matches("\\w{3}")); //出现了4次,所以返回false
// {n,} 代表至少出现n次
System.out.println("abcd".matches("\\w{3,}")); // 出现了4次,所以返回true
System.out.println("ab".matches("\\w{3,}")); // 出现了2次,所以返回false
System.out.println("abcde徐".matches("\\w{3,}"));
//出现了5次,但是有汉字,所以返回false
// {n,m} 代表出现n到m次
System.out.println("abc232d".matches("\\w{3,9}"));//出现了7次,所以返回true
}
}
4. 补充符号
符号 | 含义 |
---|---|
(?!) | 忽略大小写 |
() | 分组 |
public class Test2 {
public static void main(String[] args) {
//其他几个常用的符号:(?i)忽略大小写 、分组:()
System.out.println("abc".matches("(?i)abc")); //表示abc忽略大小写,所以返回true
System.out.println("ABC".matches("(?i)abc")); //表示abc忽略大小写,所以返回true
System.out.println("aBc".matches("a((?i)b)c")); //表示b忽略大小写,所以返回true
System.out.println("ABc".matches("a((?i)b)c"));
//表示b忽略大小写,a不忽略大小写,所以返回false
}
}
小案例
public class Test2 {
public static void main(String[] args) {
// 需求1:要求要么是3个小写字母,要么是3个数字。
System.out.println("abc".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("ABC".matches("[a-z]{3}|\\d{3}")); // false
System.out.println("123".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("A12".matches("[a-z]{3}|\\d{3}")); // false
// 需求2:必须是”我爱“开头,中间可以是至少一个”编程“,最后至少是1个”666“
System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));//true
System.out.println("我爱编程编程66666".matches("我爱(编程)+(666)+"));
//false,因为出现了不该出现的66
System.out.println("我爱编程编666666".matches("我爱(编程)+(666)+"));
//false,因为出现了不该出现的编
}
}
作用(常见用途)
1. 校验数据格式是否合法
使用String类的matches(“正则表达式”)实例方法
方法 | 说明 |
---|---|
public boolean matches( String regex ) | 是String类的实例方法,根据入参的正则表达式判断是否与当前的String对象匹配,如果匹配返回true |
案例:校验用户输入的电话、邮箱、时间是否合法
public class Test5 {
public static void main(String[] args) {
while (true) {
System.out.println("判断手机号或座机号请输入1:");
System.out.println("判断邮箱请输入2:");
System.out.println("判断时间请输入3:");
System.out.println("退出请输入其他数字:");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
switch (i){
case 1:
checkPhone();
break;
case 2:
checkEmail();
break;
case 3:
checkTime();
break;
default:
System.out.println("退出程序");
return;
}
}
}
//ctrl+alt+t 快速生成try catch等
public static void checkPhone(){
//手机号常见格式:18676769999 第一位为1,第二位为3-9,后面9位为0-9
// 座机号常见格式:010-8424242424 0104644535
// 第一位为0,第二到4位为0-9,-可有可无,-后第一位必须为1-9,后面7到8位为0-9
while (true) {
System.out.println("请输入手机号码或座机号:");
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
String regex = "(1[3-9]\\d{9})|(0\\d{2,3}-?[1-9]\\d{7,8})";
if (s.matches(regex)) {
System.out.println("是手机号码或座机号");
System.out.println("============================");
break;
}
else {
System.out.println("不是手机号码或座机号");
}
}
}
public static void checkEmail(){
/*
邮箱常见格式:
d1ei0009@163.com
25143242@qq.com
itheimad@itcast.com.cn
2位以上的字母、数字、下划线 + @ + 2位以上的字母、数字、下划线 + 一次或两次 . + 2-10位字母
*/
while (true) {
System.out.println("请输入邮箱:");
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
String regex = "\\w{2,}@\\w{2,}(\\.\\w{2,10}){1,2}";
if (s.matches(regex)) {
System.out.println("是邮箱");
System.out.println("============================");
break;
}
else {
System.out.println("不是邮箱");
}
}
}
public static void checkTime(){
//时间常见格式:2020-10-10 10:10:10
while (true) {
System.out.println("请输入时间:");
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
String regex = "\\d{4}[-年][01]?\\d[-月][0123]?\\d[-日]\\s*" +
"[012]?\\d[时:][0-5]?\\d[:分][0-5]?\\d秒?";
if (s.matches(regex)) {
System.out.println("是时间");
System.out.println("============================");
break;
}
else {
System.out.println("不是时间");
}
}
}
}
2. 在一段文本中查找满足要求的内容
使用Pattern类的实例和Matcher类的实例
方法 | 说明 |
---|---|
public static Pattern compile( String regex ) | 将正则表达式编译成Pattern对象 |
public java.util.regex.Matcher matcher( CharSequence input ) | 入参为等待爬取的原数据,将字符串与Pattern对象进行匹配,返回Matcher对象 |
public boolean find() | Matcher对象的方法,如果找到匹配项,返回true |
public String group() | Matcher对象的方法,将找到的匹配项返回 |
案例:将下面文本中的电话、邮箱、座机号码、热线电话爬取出来
来黑马程序员学习Java,
电话:18669668888,18699997777,
或者联系邮箱:boniu@itcast.cn,
座机电话:01036517895,010-98951256,
邮箱:bozai@itcast.cn,
邮箱:dlei0009@163.com,
热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090
public class Test6 {
public static void main(String[] args) {
String data = " 来黑马程序员学习Java,\n" +
" 电话:18669668888,18699997777\n" +
" 或者联系邮箱:boniu@itcast.cn,\n" +
" 座机电话:01036517895,010-98951256\n" +
" 邮箱:bozai@itcast.cn,\n" +
" 邮箱:dlei0009@163.com,\n" +
" 热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
//定义正则表达式即爬取的规则
String phoneRegex = "(1[3-9]\\d{9})|(0\\d{2,3}-?[1-9]\\d{7,8})";
String emailRegex = "(\\w{2,}@\\w{2,}(\\.\\w{2,10}){1,2})";
String reXianPhoneRegex = "(400-?\\d{3,7}-?\\d{3,7})";
String regex = phoneRegex + "|" + emailRegex + "|" + reXianPhoneRegex;
//定义Pattern对象,将正则表达式编译成Pattern对象
Pattern pattern = Pattern.compile(regex);
//定义Matcher对象,将字符串与Pattern对象进行匹配
Matcher matcher = pattern.matcher(data);
//调用Matcher对象的find方法进行匹配
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
/*
result:
18669668888
18699997777
boniu@itcast.cn
01036517895
010-98951256
bozai@itcast.cn
dlei0009@163.com
400-618-9090
400-618-4000
4006184000
4006189090
*/
3. 用于搜索替换
使用String类的实例方法replaceAll
方法 | 说明 |
---|---|
public String replaceAll(@NotNull String regex, @NotNull String replacement ) | 根据第一个入参的正则表达式寻找当前String对象中的匹配项,替换成第二个入参,返回替换后的String对象 |
案例一:
请把“古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴”中间的非中文字符替换成 “-”
public class Test7 {
public static void main(String[] args) {
String s = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
String regex = "\\w+";
String s1 = s.replaceAll(regex, "-");
System.out.println(s1);
}
}
//古力娜扎-迪丽热巴-马尔扎哈-卡尔扎巴
案例二(扩展):
某语音系统,收到一个口吃的人说的“我我我喜欢编编编编编编编编编编编编程程程!”需要优化成“我喜欢编程!”。
public class Test8 {
public static void main(String[] args) {
String s = "我我我喜欢编编编编编编编编编编编编程程程!";
/*
* (.)表示一组:.匹配任意字符的。
* \\1 :为(.)这一组声明一个组号:1号
* +:声明必须是重复的字
* $1可以取到第1组代表的那个重复的字
*/
String regex = "(.)\\1+";
String s1 = s.replaceAll(regex, "$1");
System.out.println(s1);
}
}
//我喜欢编程!
4. 分割内容
使用String类的实例方法split
方法 | 说明 |
---|---|
public String[ ] split( @NotNull String regex ) | 根据输入的正则表达式将字符串分割,返回分割后的String数组 |
案例:
请把“古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴”中的人名获取出来。
public class Test9 {
public static void main(String[] args) {
String s = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
String regex = "\\w+";
String[] ss = s.split(regex);
System.out.println(Arrays.toString(ss));
}
}
//[古力娜扎, 迪丽热巴, 马尔扎哈, 卡尔扎巴]
异常
异常
程序出现的问题
异常的体系
异常的分类
1.Error(开发人员无需关注)
代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,Sun公司会把这些问题封装成Error对象给出。
Error并不是开发人员所需要关注的,而是sun公司自己使用的。
2.Exception(开发人员需要关注的)
代表开发人员开发的程序可能出现的问题,通常用Exception以及它的孩子来封装程序出现的问题。
可以分为运行时异常和编译时异常
运行时异常(体系中的RuntimeException)
RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常。即在程序编写阶段不会报错,不影响程序的启动,只有当程序运行起来了才会报错
如数组越界异常
public class Test1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
}
}
在编译阶段不会报错,在程序运行后在控制台会出现下面的异常
编译时异常(体系中的其他异常)
编译阶段就会出现的错误,如果不解决无法编译通过,即无法启动程序
如日期解析异常
在编译阶段就会报错,如果不处理,编译无法通过,也就无法运行程序
异常的作用
- 可以用来查询系统Bug的关键信息,因为会在控制台显示
- 异常可以作为方法内部的一种特殊返回值,将异常抛给调用该方法的上层调用者,以便通知上层调用者底层的执行情况
自定义异常
Java无法为这个世界上全部的问题都提供异常类来代表,如果企业自己的某种问题,想通过异常来表示,
以便用异常来管理该问题,那就需要自己来定义异常类。
自定义异常种类
1.自定义运行时异常RuntimeException
-
当错误不是很严重时,不需要强烈提醒时,使用自定义运行时异常
-
该异常编译阶段不报错,运行时出现该异常问题才可能报错
写法
- 定义一个异常类继承RuntimeException类
- 重写构造器
- 通过throw new异常类(Xxx)来创建异常对象并抛出
/*
1.定义一个异常类继承RuntimeException类,定义运行时异常
*/
public class AgeIllegalRuntimeException extends RuntimeException{
/*
2.重写构造器
*/
public AgeIllegalRuntimeException() {
}
//message为自定义的异常信息
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
public class Test1 {
public static void main(String[] args) {
//年龄不能为负数,会在运行时报异常,但是不会在编译阶段检查并报错,不强制要求处理该异常
getAge(-1);
}
public static void getAge(int age){
if (age >= 0){
System.out.println("年龄为:" + age);
}else {
/*
3.通过throw new异常类(Xxx)来创建异常对象并抛出
*/
throw new AgeIllegalRuntimeException("年龄不能为负数");//自定义的异常信息
}
}
}
控制台的报错信息:
2.自定义编译时异常Exception
- 当错误特别严重,需要强烈提醒时,使用自定义编译时异常
- 编译阶段不处理就会报错
写法
- 定义一个异常类继承Exception类
- 重写构造器
- 通过throw new异常类(Xxx)来创建异常对象并抛出,使调用者可以获得异常信息
- 在当前方法中还要使用throws抛出异常给调用者,要求调用者必须处理
/*
* 1.定义一个异常类继承Exception类,定义编译时异常
*/
public class AgeIllegalException extends Exception{
/*
* 2.重写构造器
*/
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
public class Test2 {
public static void main(String[] args) {
//年龄不能为负数,会在编译阶段就报错,如果不处理,无法运行程序
getAge(-1);//会标红,即编译错误
}
/*
4.在当前方法中还要使用throws抛出异常给调用者,要求调用者必须处理这个异常
*/
public static void getAge(int age) throws AgeIllegalException {
if (age >= 0){
System.out.println("年龄为:" + age);
}else {
/*
3.通过throw new异常类(Xxx)来创建异常对象并抛出,使调用者可以获得异常信息
*/
throw new AgeIllegalException("年龄不能为负数");//自定义的异常信息
}
}
}
会强制要求处理异常,如果不处理编译不通过,见下图
异常的处理
ctrl + alt + t
异常处理的基础语法
1.抛出异常(throws)
在方法上使用throws关键字抛出异常,将方法内部出现的异常抛给上一层调用者
基础语法
修饰符 返回值 方法名 (参数列表) throws 异常1,异常2 ...{
方法体
}
建议使用的语法
修饰符 返回值 方法名 (参数列表) throws Exception{//不论多少异常只写一个throws Exception
方法体
}
因为所有的异常均继承自Exception,所以Exception可以代表一切异常
2.捕获异常
捕获程序出现的异常或者调用的方法抛给当前程序的异常
基础语法
try{
//可能发生异常的代码
}catch(异常变量1 变量2){
处理异常1
}catch(异常变量2 变量2){
处理异常2
}...
建议使用的语法
try{
//可能发生异常的代码
}catch(Exception e){
e.printStackTrace();
}//与上文类似,只要Exception e就可以捕获一切异常
常见处理方式
异常处理流程
异常捕获后的处理方法
经过上述的异常处理流程后,此时已经捕获到了异常,需要处理捕获后的异常。
捕获后的异常的处理方式分为以下两种:
1. 捕获异常后记录异常并响应合适的信息给用户
普通写法
public class Test3 {
public static void main(String[] args) {
//这种处理异常捕获异常的方法是记录异常并响应合适的信息给用户
try {
B();
} catch (ParseException e) {
//如果执行时时间格式与SimpleDateFormat传入的参数不一致,就会执行下面语句
System.out.println("你解析的时间格式有问题");//响应合适的信息给用户
e.printStackTrace();//记录异常信息
} catch (FileNotFoundException e) {
//如果执行时找不到文件,就会执行下面语句
System.out.println("你要找的文件不存在");//响应合适的信息给用户
e.printStackTrace();//记录异常信息
}
}
public static void B() throws ParseException, FileNotFoundException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date parse = sdf.parse("1999-0909");//ParseException也是编译时异常
C();
}
public static void C() throws FileNotFoundException {//抛出异常给方法B
//读取文件异常是编译时异常
FileReader fr = new FileReader("d:/a.txt");
}
}
控制台输出:
没有出现文件找不到的提示信息是因为代码在B方法中已经发现异常并报错,不会再执行C
如果B中没有出现异常,则会运行C方法,如果C中出现错误,会输出下面的结果
如果都没有问题,则两个错误信息都不会出现
优化写法
//上面代码中的B和C方法没有变化,不在赘述,仅仅优化处理异常的方法
public class Test3 {
public static void main(String[] args) {
try {
B();
} catch (Exception e) {//用Exception e代表所有的异常
System.out.println("你当前错误有问题");//响应合适的信息给用户
e.printStackTrace();//记录异常信息
}
}
}
2. 捕获异常并尝试重新修复
需求:调用一个方法,让用户输入一个合适的价格后返回并输出
未捕获异常修复前:
public class Test4 {
public static void main(String[] args) {
System.out.println(getMoney());
}
public static double getMoney() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入金额:");
double money = sc.nextDouble();
if (money < 0) {
System.out.println("金额不能为负数");
}else {
return money;
}
}
}
}
当输入的金额不是duoble类型时会报以下异常,这是由于sc.nextDouble(),所以可以尝试修复
修复后:
//只是修改了主函数达到修复的效果,getMoney()方法没有改变,不再赘述
public static void main(String[] args) {
//使用循环来解决异常
/*
捕获异常后,如果发生异常会给用户提示并通过while循环重新执行程序,直到用户输入正确的数据,
然后通过break跳出循环并终止程序
*/
while (true) {
try {
System.out.println(getMoney());
break;
} catch (Exception e) {
System.out.println("输入的数据有误,请重新输入");
}
}
}