1.广义异常(Throwable)的继承体系
两大类:不可通过程序恢复中断的和可通过程序恢复中断的
Java中所有“程序问题”的父类是java.lang.Throwable类,所有的异常类型都是它的子类,它的子类按照是否可通过程序中断的特点,分为两个体系
Error:不可通过程序恢复中断的。当发生类似异常时,通常认为造成错误的原因是由于硬件原因,通过程序即使恢复了运行,程序也会由于硬件原因无法执行。所以此类异常发生,只能等待硬件的修复。
Exception:可通过程序恢复中断的。当发生此类异常时,通常认为程序的逻辑不严谨,可以通过排查程序逻辑进行处理。
2.狭义异常(Exception)的分类
两类: 运行时异常(RuntimeException)
检查时异常(除了RuntimeException类及其子类以外的其他Exception子类)
运行时异常的特点是编译时不强制要求对异常发生给出预案,一旦代码逻辑错误,程序运行时会发生异常。
常见的运行时异常:
数学运算异常ArithmeticException,整数除法或取余操作时除数为0。
空指针引用异常NullPointerException,调用了null值对象的属性和方法
类型强制转换异常ClassCastException,下溯造型逻辑错误
下标越界异常IndexOutOfBoundsException,数组或字符串下标越界
非法参数异常IllegalArgumentException,在框架的一些数据绑定过程中绑定了错误的参数
检查性异常的特点是编译时强制要求给出异常发生时的预案,一旦运行时发生异常,立即执行预案。需要注意的是,检查性异常不代表异常一定发生,它只是要求运行含有它的代码时,必须给出异常发生时的处理预案。
对于运行时异常,绝大部分都是由于程序的逻辑错误造成的,所以运行时异常一般通过完善程序逻辑来 处理,比如发生空指针异常时我们应该做的不是进行异常处理,而是检查空指针发生的原因,从源头规 避null对象的产生。或者对null对象有一套专门的处理逻辑。
检查性异常发生的原因可能不是逻辑错误,此时我们需要通过设置预案来防止程序因为异常造成的中 断,恢复程序的继续执行。异常的预案有两种方式:抛出(上报)和捕获
3.异常管理-抛出机制
异常的第一个处理机制是抛出,也叫上报,其作用是如果发生了异常,不进行任何处理,而是将异常抛出给调用异常所在方法的调用者,由方法调用者来进行处理。
某个方法如果需要抛出异常,需要通过throws关键字在方法上声明抛出了哪些异常,方法的调用者需要对异常进行处理(继续抛出或就地捕获)
例:调用线程休眠方法会抛出线程中断异常InterruptedException
public static void test() throws InterruptedException{
Thread.sleep(1000);
}
public static void main(String[] args){
test();//调用test方法会要求main方法强制处理InterruptedException
}
某些情况下我们可能还需要人为制造异常,提示方法调用者程序可能会出现问题,通过 throw 关键字抛 出一个异常对象。
示例:设计一个方法,输入两个正整数,返回它们的和。如果输入参数不是正整数,抛出异常提示方法 调用者
public static int add(int a,int b) throws Exception{
if(a<0||b>0){
//参数不正确
throw new Exception();//人为制造异常
}
return a+b;
}
人为制造异常很多时候是为了配合项目的整体异常管理,项目中可能对异常有全局统一的管理,根据异常生成相应的UI显示,所以发生问题我们可能会抛出异常。
4.异常管理-捕获机制
如果任由异常持续的抛出,实际上最终没有任何程序对异常进行了处理,那么异常最终会抛给JVM。 JVM只能将异常的栈信息打印出来,这与异常没有处理的性质是一样的。
完善的程序应该在合理的时机抛出异常,合理的时机进行捕获异常,让程序的执行得到恢复。异常的捕 获由以下关键字相关代码块组成
try...catch...finally...
它们的使用有如下规则:
- 必须有try代码块,且一组异常处理中只有一个try,
- try不能单独使用,必须与至少一个catch或finally块组合使用
- catch代码块的数量任意(可以没有),表述异常类型范围越大的catch应该在多个catch的越下方
- finally最多只能写一个
一个比较标准的捕获处理如下:
try{
//可能发生异常的代码
}catch(IOException e1){
//发生IO异常时的处理程序,此处可以是任意其他异常类型,也可以没有这块
} catch(Exception e2){
//发生其他异常时的处理程序,此处可以是任意其他异常类型,也可以没有这块
}finally{
//无论是否发生异常,都要执行的代码
}
这段代码的实际执行如下
- 当try中没有发生任何异常,将try中的代码执行完毕,然后执行finally的代码
- 当try中发生了IOException异常,执行try发生异常前的代码,然后执行IO异常的catch,最后是 finally
- 当try中发生了其他异常,执行try发生异常前的代码,然后执行Exception的catch,最后是finally
总结一下
- try中编写可能发生异常的代码
- catch中编写异常发生时的预案代码
- finally中编写无论异常是否发生,都要执行的代码。
示例:异常抛出和捕获
public static void test() throws InterruptedException,FileNotFoundException {
Thread.sleep(1000);
FileInputStream fis = new FileInputStream("c:/1.txt");
}
public static void main(String[] args) {
try {
test();
} catch (FileNotFoundException e) {
System.out.println("文件没有找到");
} catch (InterruptedException e) {
System.out.println("线程意外中断");
}
try {
test();
} catch (FileNotFoundException | InterruptedException e) {
System.out.println("发生了异常");
}
try {
test();
}catch (Exception e) {
System.out.println("发生了异常");
}
try {
test();
} catch (FileNotFoundException e) {
System.out.println("文件没有找到");
} catch (InterruptedException e) {
System.out.println("线程意外中断");
} catch (Exception e) {
System.out.println("发生了异常");
}
}
5.自定义异常
自定义异常:继承Throwable或者它的子类Exception的用户自己定义的异常类。
自定义异常的步骤:
- 创建自定义异常类
- 在方法中通过throw抛出异常对象
- 如果在当前方法中对抛出的异常对象作处理,可以使用try-catch语句块捕获抛出异常对象并且处 理,否则要在方法的声明处通过throws关键字指明要抛出给方法调用者的异常。
- 在出现异常方法的调用者中捕获并处理异常
class CircleException extends Exception { //自定义异常类
double radius;
CircleException(double r) { //有参构造方法
radius=r;
}
public String toString() {
return "半径 r="+radius+"不是一个正数";
}
}
class Circle{
private double radius;
public void setRadius(double r) throws CircleException{
if(r<0){
throw new CircleException(r);
}else{
radius=r;
}
}
public void show(){
System.out.println("圆面积="+3.14*radius*radius);
}
}
public class Demo01{
public static void main(String args[]){
Circle cir=new Circle();
try{
cir.setRadius(-2);
}catch(CircleException e){
System.out.println("自定义异常:"+e.toString()+" ");
}
cir.show();
}
}
6.Try-With-Resources
Java库中有很多资源需要手动关闭,例如打开的文件、连接的数据库等。在java7之前都是try-finally 的方式关闭资源,try后面总是跟着一个“{”。
try-finally 块进行资源使用及手动关闭的方式存在几个问题:
- 容易忘记关闭资源,从而引发内存泄漏;
- 资源比较多的时候,代码嵌套层次较深,代码可读性不佳;
- try 块与 finally 块同时发生异常时,存在异常压制问题。
import java.io.*;
public class Demo01 {
public static void main(String[] args) {
copy("D:/A.java","E:/B.Java");//复制文件
}
private static void copy(String src, String des) {
InputStream in = null;
OutputStream out = null;
try{
in = new FileInputStream(src);
out = new FileOutputStream(des);
byte[] buff = new byte[1024];//创建一个长度为1024字节的字节数组
int n;
//从输入流一次最多流入buff.length个字节的数据到buff中,直到文件末尾结束
while ((n=in.read(buff))>=0){
//将数组buff中的数据从0位置开始,长度为n的字节输出到输出流中
out.write(buff,0,n);
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(in!=null){
try{
in.close();
}catch (IOException e){
e.printStackTrace();
}
}
if(out!=null){
try{
out.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
上面实现过程非常的杂乱冗长。
Java7之后,推出了Try-With-Resources声明代替之前的方式,try后跟括号"(",括号内的部分称为资源 规范头。资源规范头中可以包含多个定义,通过分号进行分隔。规范头中定义的对象必须实现 java.lang.AntoCloseable 接口,这个接口中有一个 close() 方法,因此无论是否正常退出try语句 块,这些对象都会在try语句块运行结束之后调用close方法,从而替代以前的在finally中关闭资源的功 能,且不需要冗长的代码,另外,Try-With-Resources中的try语句可以不包含catch或者finally语句块 而独立存在。
public class Demo01{
public static void main(String[] args){
copy("D:/A.java","E:/B.Java");//复制文件
}
private static void copy(String src,String des){
try(InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(des)) {
byte[] buff =new byte[1024];//创建一个长度为1024字节数组
int n;
//从输入流一次最多流入buff.length个字节的数据到buff中,直到文件末尾结束
while((n=in.read(buff))>=0){
//将数组buff中的数据从0位置开始,长度为n的字节输出到输出流中
out.write(buff,0,n);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
7.三层架构异常处理
1. dao层不捕获异常、不抛出异常:spring框架将底层的数据库checked异常封装成unchecked异常 了
2. service层捕获异常,并抛出自定义unchecked异常,抛出的异常定义状态码:checked异常默认 情况事务不会回滚 . (如果不抛出异常事务不会回滚)
3. controller层捕获unchecked异常, 处理返回结果.
@PostMapping("/backup")
@ApiOperation(value = "数据备份")
@ApiResponses(
@ApiResponse(code = 200,message = "备份成功")
)
public ResponseResult dataBackUp(@RequestParam String onlineUserName){
try {
if (iMrvDataBackupService.dataBackup(onlineUserName)){
return ResponseResult.success();
}
return ResponseResult.fail("备份异常");
}catch (Exception e){
log.error("备份异常", e);
return ResponseResult.fail("备份异常,"+e.getMessage());
}
}
注意:SpringBoot项目全局异常处理:一般在使用@ControllerAdvice+@ExceptionHandler 全局统一处理并捕获异常返回前端。