Java异常处理机制
1.概述
定义:程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
异常体系结构
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。Throwable又派生出Error类和Exception类。
error:Error类以及它的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理
(如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢,内存溢出等)
异常:Exception以及它的子类,代表程序运行时发生的各种不期望发生的事件。可以被java异常处理机制处理,是异常处理的核心。
(例如: 空指针访问,试图读取不存在的文件, 网络连接中断,数组越界)
exception又分为运行时异常和受检异常
受检异常:
当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检异常
运行时异常:
都是RuntimeException类及其子类异常,如IOException、SQLException等以及用户自定义的Exception异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
Why:为什么有非检查异常?
你想想非检查异常都有哪些?NullPointerException,IndexOutOfBoundsException,VirtualMachineError等,这些异常你编译的时候检查吗?再说了,明明可以运行时检查,都在编译的时候检查,你写的代码还能看吗?而且有些异常只能在运行时才能检查出来,比如空指针,堆溢出等。
2.处理异常的机制
异常追踪栈:
异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。
异常最先发生的地方,叫做异常抛出点)
当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用它的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
异常处理的处理机制
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
对于检查异常/非检查异常,都有2种不同的处理方式: 1、try...catch...finally语句块。 2、在函数签名中使用throws 声明交给函数调用者caller去解决。
抛出异常: Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
异常对象的生成: 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出。 由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样。
捕获(catch)异常: 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。
异常处理机制一:throws
使用方法略
需要注意的地方:
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行、如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。 3、在try块中打开资源,在finally块中清理释放这些资源。
异常处理机制二:throws
throws 在方法签名中,声明方法可能抛出什么异常,让调用者来处理这些异常
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉或并不能确定如何处理这种异常,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理,否则编译不通过。
手动抛出异常:throw
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
在java中,提供了一个 throw 关键字,它用来抛出一个指定的异常对象。 在当前方法中不处理,抛给调用者处理。
throw语句的后面可以抛出的异常必须是Throwable或其子类的实例。
throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点
3.异常的链化
假设B模块完成自己的逻辑需要调用A模块的方法,如果A模块发生异常,则B也将不能完成而发生异常,但是B在抛出异常时,会将A的异常信息掩盖掉,这将使得异常的根源信息丢失。异常的链化可以将多个模块的异常串联起来,使得异常信息不会丢失。
异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫它根源异常(cause)。
4.自定义异常
如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展(extends)自RuntimeException。
按照国际惯例,自定义的异常应该总是包含如下的构造函数: 一个无参构造函数 一个带有String参数的构造函数,并传递给父类的构造函数。 一个带有String参数和Throwable参数,并都传递给父类构造函数。 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
public IOException() {
super();
}
public IOException(String message) {
super(message);
}
public IOException(String message, Throwable cause) {
super(message, cause);
}
public IOException(Throwable cause) {
super(cause);
}
自定义异常构建
首先写一个自定义异常,继承Exception,代码如下:
public class MyException extends Exception {
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(Throwable cause) {
super(cause);
}
使用自定义异常
public class Test { public static void main(String[] args) { A a = new A(); try { a.show(-2); } catch (MyException e) { System.out.println(e.getMessage()); } } }
class A { public void show(int num) throws MyException { if (num < 0) { MyException me = new MyException("异常:" + num + "不是正数"); throw me;//抛出异常,结束方法show()的执行 } System.out.println(num); }
}
· ··对于需要处理的可能异常,判断后抛出
class AgeException extends Exception { public AgeException(String message) { super(message); } }
public class Example6_9 { public static void input() { Scanner scanner = new Scanner(System.in); System.out.println("请输入姓名:"); String name = scanner.next();//接收键盘上姓名的输入 System.out.println("请输入年龄:");
while (scanner.hasNext()) {
try {
int age = scanner.nextInt();//接收键盘上年龄的输入
if (age < 0) {
throw new AgeException("年龄不能为负数");
}
System.out.println("姓名" + name);
System.out.println("年龄" + age);
break;
} catch (AgeException e) {
System.out.println(e.getMessage() + "请重新输入:");
}
}
}
5.全局异常处理
//方法增强,只有@RsetController注解下的方法才会调用这个异常处理模式
@RestControllerAdvice(annotations = RestController.class)
//这个注解开启日志功能
@Slf4j
//这个注解注入到spring容器
@Component
public class GlobalExpectionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
//处理参数效验的异常
public Result handMethodArgumentNotValidException(MethodArgumentNotValidException e){
//把异常对象e的message变成字符串,在日志上输出
log.error("参数效验异常!",e);
//给前端返回结果
return new Result(401,e.getMessage(),null);
}
}
RestControllerAdvice aop处理,对于controller层抛出的异常(抛给MethodArgumentNotValidException,这个自定义异常类)进行统一处理