异常
异常的种类
- 编译异常
- 运行异常
异常的三种处理方式:
- jvm默认处理:把异常信息以红色字体打印在控制台,并结束程序运行
- 捕获异常:try{}catch(){},一般用在方法调用处,能让代码继续往下运行
- 抛出异常:throws 异常类名,throw new 异常 。(前者提示异常种类,后者创建异常对象并抛出,用于捕获异常的时候被catch接收),一般写在方法中,出现异常方法没有运行下去的意义,采取抛出处理。让该方法结束运行并告诉调用者出现了问题。
捕获和抛出的核心区别:捕获是为了不让程序停止,抛出是为了告诉调用者方法出错了
捕获异常
-
如果try中没有遇到问题,怎么执行?
答:会把try里面的正常代码全部执行完毕,不会执行catch里面的异常 -
如果try中可能会遇到多个问题,怎么执行?
答:会写多个catch与之对应,如果这多个异常存在子父类关系,那么父类一定要写在下面
- 在JDK7之后,我们可以在catch中同时捕获多个异常,中间用|进行隔开
- 表示如果出现了A异常或B异常的话,采取同一种处理方案
-
如果try中遇到的问题没有被捕获,怎么执行?
答:相当于try...catch的代码白写了,最终还是会交给虚拟机进行处理。 -
如果try中遇到了问题,那么try下面的其他代码还会执行吗?
答:不会执行了,直接跳转到catch当中,执行catch里面的语句体,如果没有对应的catch与之匹配,即同问题3,问题没有被捕获,那么交给JVM处理
异常中的常见方法
String message = e.getMessage(); //返回异常的信息 有返回值,需要变量接收
String str e.toString(); //返回此异常可抛出的简短描述(异常名字,异常的信息) 有返回值,需要变量接收
e.printStackTrace(); //把异常的名字和信息,还有异常所在的位置输出在控制台,仅仅是打印信息,不会停止程序运行 无返回值,直接用对象调用
第三个方法展示的信息最多,因此我们常用第三个方法
抛出异常
throws:写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会哪些异常,多个异常之间用逗号隔开
例:
public void 方法名() throws 异常类名1,异常类名2...{}
- 编译时异常:必须要写
- 运行时异常:可以不写
throw:写在方法内,结束方法,手动抛出异常对象,交给调用者,方法中下面的代码不再执行
例:
public void 方法(){
throw new NULLPointerException();
}
自定义异常
意义:为了让控制台的报错信息更加的见名知意,大白话:当不存在这种类型的异常类时,为了让异常的名字更加的符合代码出现的异常情况而自定义的一种异常类
- 定义异常类
- 写继承关系(继承已存在的Exception或者RuntimeException)分别对应编译时异常、运行时异常,编译时异常一定要在方法名后面抛出
- 空参构造
- 带参构造
方法内打印错误 + 外部 continue 重试)在简单、小型、自己写的代码里完全能用,为什么编程里还要专门设计「抛出异常」这个复杂机制?
① 错误信息和业务逻辑彻底分离
抛出异常后:
- 方法只负责 “发现错误并报告”
- 调用者负责 “怎么处理错误”(提示?重试?退出?)
职责清晰,不会混在一起。
② 调用者能精准知道发生了什么
你可以抛不同异常:
- ArithmeticException 算术错误
- IllegalArgumentException 参数非法
- NullPointerException 空指针
- IOException 文件读取失败
调用者可以分别捕获、分别处理,而不是靠猜 -1、null、false 代表什么。
③ 错误可以一层层往上传递,不用每层都判断
这是异常最强大的地方:
A 调用 B,B 调用 C,C 出错。
- 你的方案:A、B、C 每层都要判断返回值是不是错的
- 异常方案:C 抛异常 → 直接飞到 A 处理,中间不用管
代码干净到爆炸。
④ 适合团队开发、框架、大型项目
别人调用你的方法时:
- 不需要看你内部代码
- 不需要记你用 -1 还是 0 代表错误
- 直接捕获异常就行
标准化、可维护、可扩展。
抛出异常,根本目的不是为了给人看报错文字,而是为了让「程序代码」能捕获到错误,然后程序自己做逻辑处理。
- System.out.println 只是打印一句话给程序员眼睛看,程序本身没感知、没法做逻辑判断。
- throw 异常 是把错误封装成一个对象扔出去,让上层代码 try-catch 抓到,然后程序自动处理:
- 重新输入(continue)
- 给前端返回错误码
- 记录日志
- 跳过当前流程、兜底默认值
1. 先定义两个词(超级简单)
- 业务逻辑:你的核心功能,正经干活的代码
比如:做除法计算 a / b - 错误处理:出问题后的补救操作,擦屁股的代码
比如:提示用户、重新输入、报错、记日志
2. 没分离(println 写法 = 烂写法)
干活的人,把报错也自己干了,写死了!
// 方法:只负责计算的业务逻辑
public static int div(int a,int b){
if(b==0){
// 【错误处理写死在业务方法里】
System.out.println("除数不能为0");
return 0;
}
return a / b;
}
问题在哪?
- div 本来只应该负责计算(业务)
- 现在它强行包办了报错打印(错误处理)
耦合死了:
以后你想换报错方式,必须改业务代码
- 想弹窗?改方法
- 想返回 JSON 错误码?改方法
- 想记录日志?改方法
业务代码被一堆报错逻辑污染,乱七八糟。
3. 完全分离(throw 异常 = 正规写法)
干活的只干活,擦屁股的交给调用者
// 业务层:只专注【判断+计算】,完全不处理错误
public static int div(int a,int b){
if(b==0){
// 只报信,不处理!
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
// 调用层:专门负责【错误处理】
try{
// 执行业务
div(a,b);
}catch(Exception e){
// 想怎么处理就怎么处理,随便改!
System.out.println("重新输入!");
// 弹窗、日志、返回前端、重试,全部自由切换
}
4. 什么叫「完全分离」?
业务层(方法内部)
只做三件事:
- 判断参数对不对
- 不对就抛异常(告知出错)
- 对就正常干活
绝不打印、绝不提示、绝不重试、绝不处理
控制层(调用处)
只负责:
出错了怎么收拾残局
5. 分离的终极好处(一句话封神)
业务代码永远不用改!
错误处理方式可以随便换!
- 控制台程序 → 打印提示
- Web 后端 → 返回 JSON 错误码
- 桌面程序 → 弹窗提示
- 日志系统 → 记录报错堆栈
同一个 div 方法,啥都不用改,适配所有场景!
6. 最终极简口诀
- 不分离:方法自己报错,写死、僵硬、改业务代码
- 完全分离:方法只抛错,上层随便处理,灵活、规范、工程化
两种写法核心对比总结
-
耦合写法(println):业务方法包揽所有报错逻辑,报错方式固定死,无法适配多场景,代码复用性极差,仅适合新手测试。
-
解耦写法(throw异常):单一职责,业务方法只负责校验规则,错误处理灵活可控,同一个登录方法,可适配控制台、网页、客户端所有场景,是企业开发标准。
最终核心真谛
方法只管「对不对」,不管「怎么办」。 对的就正常执行业务,错的就抛异常,怎么办、怎么善后,全权交给调用者决定,这就是异常机制的核心精髓。
浙公网安备 33010602011771号