1.定义(什么是异常?)
异常是例外,是一个程序在执行期间发生的事件,它中断正在执行程序的正常指令流。软件开发过程中,很多情况都会导致异常的产生,例如对负数开平方根、对字符串做算术运算、操作数超出范围、数组下标越界等。
2.异常简单例子
(1)除数为零的例子
/**
* @Author: qp
* @Time: 2021/8/24 21:11
* @Description
*/
public class Example6_1 {
public static void main(String[] args){
int a= 0;
System.out.println(5/a);
}
}
输出结果:由于除数不能为0,所以程序运行时出现了除以0溢出的异常事件。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.chapter6.Example6_1.main(Example6_1.java:11)
(2)类型转换错误
/**
* @Author: qp
* @Time: 2021/8/24 21:14
* @Description
*/
public class Example6_2 {
public static void main(String[] args){
String str = "jack";
System.out.println(str+"年龄是:");
String s = "20L";
int age = Integer.parseInt(s);
System.out.println(age);
}
}
输出结果为:报出NumberFormatException
(数字格式异常),程序提示了消息,可见提示的消息代码正常执行,而变量age没有执行,故s转换为age的时候程序异常了。
jack年龄是:
Exception in thread "main" java.lang.NumberFormatException: For input string: "20L"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at com.chapter6.Example6_2.main(Example6_2.java:13)
上面的两个程序抛出了两种异常,这两种异常是一个个的异常对象,异常对象的产生取决于产生异常的类型。此时的异常可以概括为:
异常是程序由于各种原因导致无法正常运行的一个事件,这个事件是由一个异常对象来代表的,这个对象的产生取决于产生异常的类型,可能由应用程序本身产生,也可能由Java虚拟机产生。
二、异常类的层次
checked Exception和unchecked Exception的区别:
要想明白Java中checked Exception和unchecked Exception的区别,我们首先来看一下Java的异常层次结构。
这是一个简化的Java异常层次结构示意图,需要注意的是所有的类都是从Throwable继承而来,下一层则分为两个结构,Error和Exception。其中Error类层次描述了Java运行时系统的内部错误和资源耗尽错误,这种错误除了简单的报告给用户,并尽力阻止程序安全终止之外,一般也米有别的解决办法了。
(二)unchecked异常和checked异常的区别
有了上面的认识之后,我们再来看什么是checked异常,什么是unchecked的异常。其实,Java语言规范对这两个定义十分简单,将派生于Error或者RuntimeException的异常称为unchecked异常,所有其他的异常成为checked异常。
1.异常类的层次
2.异常事件类型
异常类都是内置类Throwable的子类。Throwable类有两个子类:Error(错误)和Exception(异常)
- Error(错误):通常是灾难性的致命错误,不是程序(程序猿)可以控制的,如内存耗尽、JVM系统错误、堆栈溢出等。应用程序不应该去处理此类错误,且程序员不应该实现任何Error类的子类。
- Exception(异常):用户可能捕获的异常情况,可以使用针对性的代码进行处理,如:空指针异常、网络连接中断、数组下标越界等。
3.Exception(异常)分类
Exception(异常)又分为两类:运行时异常和编译时异常。
(1)运行时异常
RuntimeException
为Java虚拟机在运行时自动生成的异常,如被零除和非法索引、操作数超过数组范围、打开文件不存在等。此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
RuntimeException
类及其子类称为非检查型异常,Java编译器会自动按照异常产生的原因引发相应类型的异常,程序中可以选择捕获处理也可以不处理,虽然Java编译器不会检查运行时异常,但是也可以去进行捕获和抛出处理。RuntimeException类和子类以及Error类都是非受检异常。
(2)编译时异常
Exception中除RuntimeException
及其子类之外的异常,该异常必须手动在代码中添加捕获语句或一直向上抛出,来处理该异常。编译时异常也称为受检异常,一般不进行自定义检查异常。
4.常见的异常类
异常类 | 说明 |
---|---|
ClassCastException | 类型转换异常 |
ArrayIndexOutOfBoundsException | 数组越界异常 |
NegativeArraySizeException | 指定数组维数为负值异常 |
ArithmeticException | 算数异常 |
InternalException | Java系统内部异常 |
NullPointerException | 空指针异常 |
IllegalAccessException | 类定义不明确所产生的异常 |
IOException | 一般情况下不能完成I/O操作产生的异常 |
EOFException | 打开文件没有数据可以读取的异常 |
FileNotFoundException | 在文件系统中找不到文件路径或文件名称时的异常 |
ClassNotFoundException | 找不到类或接口所产生的异常 |
CloneNotSupportedException | 使用对象的clone方法但无法执行Cloneable所产生的异常 |
5.异常类的常用方法
- getMessage():返回Throwable对象的详细信息,如果该对象没有详细信息则返回null(常用打印错误信息)
- getLocalizedMessage() :返回Throwable的本地化描述,子类可能会覆盖该方法以便产生一个特定于本地的消息,对于未覆盖该方法的子类,默认返回调用getMessage()的结果
- printStackTrace():将Throwable和它的跟踪情况打印到标准错误流(常用打印详细追踪错误信息)
- toString():返回Throwable对象的类型与性质
三、Java异常处理过程
1.什么叫异常处理机制?
前面定义过:异常是程序由于各种原因导致无法正常运行的一个事件,这个事件是由一个异常对象来代表的,这个对象的产生取决于产生异常的类型,可能由应用程序本身产生,也可能由Java虚拟机产生。异常事件是 Throwable 类或其子类的实例(异常对象),异常处理机制就是当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理,异常处理机制并不能真正的修复程序的bug,只能说是防止程序崩溃,要修复程序的bug时需要我们在catch语句中对异常进行一些处理。
- 定义:Java是采用面向对象的方式来处理异常的。处理过程:
(1)抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给运行时系统(JRE)。
(2)捕获异常:JRE得到该异常后,寻找相应的代码来处理该异常。JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。 - 限制:Exception 中除 RuntimeException 及其子类之外的异常一般需要进行异常处理
2.Java异常关键字
Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally
• try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally – finally语句块总是会被执行。
• throw – 用于抛出异常。
• throws – 用在方法签名中,用于声明该方法可能抛出的异常。
3.异常处理机制类型
- 在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
- 异常处理机制类型与异常关键字的对应关系
4.Java异常处理过程——捕获异常catch
(1)定义
异常对象被系统沿着方法的调用栈逐层回溯,交给能够匹配这种异常对象的异常引用,接着对异常对象进行处理,这一过程就是捕获(catch)异常。
(2)结构语法
try {
// 程序代码块
}
catch (ExceptionType1 e){
// 对ExceptionType1的处理
}
catch (ExceptionType2 e){
// 对ExceptionType2的处理
}
...
final{
// 程序代码块
}
try
语句中存放的是可能发生异常的语句。当异常抛出时,异常处理机制负责搜寻参数与异常类型相匹配的第一个处理程序,然后进入catch语句中执行,此时认为异常得到了处理。如果程序块里面的内容很多,前面的代码抛出了异常,则后面的正常程序将不会执行,系统直接catch捕获异常并且处理
catch
语句可以有多个,用来匹配多个异常,捕获异常的顺序与catch语句的顺序有关,当捕获到对应的异常对象时,剩下的catch语句不再进行匹配,因此在安排catch语句的顺序时,首先应该捕获最特殊的异常,然后一般化。catch的类型是Java语言定义的或者程序员自己定义的,表示抛出异常的类型。异常的变量名表示抛出异常的对象的引用,如果catch捕获并匹配了该异常,那么就可以直接用这个异常变量名来指向所匹配的异常,并且在catch语句中直接引用。部分系统生成的异常在Java运行时自动抛出,也可通过throws关键字声明该方法要抛出的异常,然后在方法内抛出异常对象。
final
语句为异常提供一个统一的出口,一般情况下程序始终都要执行final语句,final在程序中可选。final一般是用来关闭已打开的文件和释放其他系统资源。try-catch-final
可以嵌套。
有4种特殊情况,finally块不会被执行:
- finally语句块中发生了异常
- 前面的代码中执行了System.exit()退出程序
- 程序中所在的线程死亡
- 关闭CPU
(3)举例
- 例1:捕获异常
class Scratch {
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}finally {
System.out.println("finally block");
}
}
}
程序输出结果为:
java.lang.ArrayIndexOutOfBoundsException: 0
finally block
输出信息表明程序中捕获到了一个ArrayIndexOutOfBoundsException类的运行时的异常,是由于main的参数args[]字符串数组没有接收到任何输入造成的。
右击程序包,选中Run As->Run Configurations,打开Run Configurations界面,在Arguments一栏填充数据“2”,再次运行,运行结果为:
a=5
finally block
重新输入参数“0”,再次以运行此程序,程序输出结果为:抛出ArithmeticException异常,ArrayIndexOutOfBoundsException异常并没有被捕获。
finally block
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.chapter6.Example6_1.main(Example6_1.java:20)
可以将程序改为:
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
catch (ArithmeticException e){
System.out.println(e);
}
finally {
System.out.println("finally block");
}
}
运行结果为:抛出ArithmeticException
java.lang.ArithmeticException: / by zero
finally block
- 例2:匹配多个异常的catch子句
public class Example6_1 {
public static void main(String[] args) {
try {
int i =Integer.parseInt(args[0]);
int a = 10/i;
System.out.println("a="+a);
}
catch (ArrayIndexOutOfBoundsException e){
System.out.println("没有输入数字串");
}
catch (ArithmeticException e){
System.out.println("分母不能为0");
}
catch (NumberFormatException e){
System.out.println("输入格式不正确");
}
catch (Exception e){
System.out.println("异常"+e);
}
}
}
考虑到该程序运行时可能产生的异常有:当没有任何输入时,会捕获到ArrayIndexOutOfBoundsException 异常;当输入值为0时,会捕获到ArithmeticException 异常,当输入格式不是整数时,会捕获NumberFormatException异常。
通常,为捕获到所有可能出现的异常,可以在处理异常的末尾,加上Exception 类,这样即可以使所有异常都被捕捉到,也可以防止想捕获具体异常时被它提前捕获。
- 例3:多重捕获
public class Example6_1 {
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int a = 10 / i;
System.out.println("a=" + a);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("没有输入数字串或输入格式不正确");
} catch (ArithmeticException e) {
System.out.println("分母不能为0");
} catch (Exception e) {
System.out.println("运行异常" + e);
} finally {
System.out.println("finally block");
}
}
}
运行程序,输出结果为:可以看出finally子句总能被执行。
有输入数字串或输入格式不正确
finally block
(4)关于return和finally的关系
- 例1:try中有return
public class Example6_2 {
public static void show(int i) {
try {
if (i == 1) {
System.out.println("one");
return;
}
if (i == 2) {
System.out.println("two");
}
return;
}catch (Exception e){
System.out.println(e);
}finally {
System.out.println("finally block");
}
}
public static void main(String[] args) {
for (int i=1;i<3;i++){
show(i);
}
}
}
输出结果为:
one
finally block
two
finally block
从程序运行结果可以看出,无论在什么位置添加return
,finally
子句都会被执行。
- 例2:catch和try中都有return
public class Example6_6 {
public static int add(int a, int b) {
try {
System.out.println(2 / 0);
return a + b;
} catch (Exception e) {
System.out.println("catch语句模块");//先执行
return 1;//最后执行
} finally {
System.out.println("finally语句模块");//再执行
}
}
public static void main(String[] args) {
System.out.println(add(1, 2));
}
}
输出结果为:
catch语句模块
finally语句模块
1
当try中抛出异常且catch中有return语句,finally中没有return语句,java先执行catch中非return语句,再执行finally语句,最后执行return语句。若try中没有抛出异常,则程序不会执行catch体里面的语句,java先执行try中非return语句,再执行finally语句,最后再执行try中的return语句。
- 例3:finally 中有return
public class Example6_6 {
public static int add(int a,int b){
try {
System.out.println(2/0);
return a+b;
}catch (Exception e){
System.out.println("catch语句模块");//先执行try中语句抛出异常接着执行此条语句
return 5;
}finally {
System.out.println("finally语句模块");//最后执行finally语句
return 2;//finally语句中的数据会覆盖try和catch中的return
}
}
public static void main(String[] args) {
System.out.println(add(1,2));
}
}
输出结果为:
catch语句模块
finally语句模块
2
finally中有return时,会覆盖掉try和catch中的return。
- 例4:finally中没有return语句,但是改变了返回值
先来看第一个例子
public class Example6_7 {
public static int set() {
int a = 1;
try {
return a;
} catch (Exception e) {
System.out.println("catch语句");
} finally {
a = 200;
}
return a;
}
public static void main(String[] args) {
System.out.println("a="+set());
}
}
可以先猜猜输出的结果是什么,接着来看第2个例子:
public class Example6_7 {
public int a;
public Example6_7 set() {
try {
this.a=10;
} catch (Exception e) {
System.out.println("catch语句");
} finally {
System.out.println("finally语句块");
this.a=200;
}
return this;
}
public static void main(String[] args) {
Example6_7 t=new Example6_7();
t.set();
System.out.println(t.a);
}
}
可以看到两段代码运行后的结果如下:
a=1
finally语句块
200
如果finally中定义的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块中之前保存的值;如果finally中定义的数据是是引用类型,则finally中的语句会起作用,try中return语句的值就是在finally中改变后该属性的值。
(5)使用场景
针对需要如何处理的异常,采用捕获的方式去处理异常。
5.Java异常处理过程——声明异常throws
(1)定义
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。用它修饰的方法向调用者表明该方法可能会抛出异常(可以是一种类型,也可以是多种类型,用逗号隔开)(位置: 写在方法名 或方法名列表之后 ,在方法体之前。)
(2)结构语法
static void pop() throws Exception1,Exception2,Exception3{
//方法体
}
(3)throws
与throw
的区别
- throw 在方法体内使用,throws 函数名后或者参数列表后方法体前
- 意义 : throw 强调动作,而throws 表示一种倾向、可能但不一定实际发生
- throws 后面跟的是异常类,可以一个,可以多个,多个用逗号隔开。throw 后跟的是异常对象。
(4)举例
public class Example6_3 {
//定义方法并抛出NegativeArraySizeException异常
static void pop() throws NegativeArraySizeException{
int[] arr = new int[-5];
}
public static void main(String[] args){
try {//处理异常信息
pop();//调用pop方法
}catch (NegativeArraySizeException e){
System.out.println("pop()方法抛出的异常");//输出异常消息
}
}
}
使用throws
关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。pop方法中没有处理异常NegativeArraySizeException,而是main函数来处理。
(5)使用场景
- 非检查异常(运行时异常)可以不使用throws关键字来声明要抛出的异常,编译也可顺利通过,但在运行时会被系统抛出。
- 受检异常(编译时异常),必须使用try-catch/throws处理,否则会导致编译错误。
- 仅当抛出了异常,该方法的调用者才必须处理或重新抛出该异常。当方法的调用者无力处理该异常时,应该继续抛出而不是直接调用方法。
- 调用方法必须遵循一个原则:若覆盖一个方法,则不能声明与覆盖方法不同的异常,声明的任何异常必须是被覆盖方法所声明异常的同类或子类。如下例子:
public class Example6_3 {
void method1() throws IOException {
}//合法
//编译错误,必须捕获或声明抛出IOException
void method2() {
method1();
}
//编译错误,必须捕获或声明抛出IOException或其父类
void method3() throws IOError {
method1();
}
//合法,声明抛出IOException
void method3() throws IOException {
method1();
}
//合法,声明抛出Exception,Exception是IOException的父类
void method4() throws Exception {
method1();
}
//合法,捕获IOException
void method5() {
try {
method1();
}catch (IOException e){
}
}
//编译错误,必须捕获或声明抛出Exception
void method6() {
try {
method1();
}catch (IOException e){
throw new Exception();
}
}
//合法,声明抛出Exception
void method7() throws Exception {
try {
method1();
}catch (IOException e){
throw new Exception();
}
}
}
6.Java异常处理过程——抛出异常throw
(1)定义
Java程序的当前方法或自身不去处理异常,选择在方法内部
使用throw
抛出一个Throwable类型的异常。
(2)结构语法
- 关键字是
throw
,throw语句抛出的是异常类对象,因此需要new
关键字创建这一异常实例,而且只能抛出一个异常实例。 throw
在方法体中,程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里到外寻找含有与其匹配的catch子句的try块。- new 的异常类名称必须是Throwable或其子类
throw new 异常类名称([描述信息])
(3)举例
- 例1
public class Example6_5 {
public static void main(String[] args) {
int a = 6;
int b = 0;
try {
System.out.println(a / b);
throw new ArithmeticException();
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
}
运行结果:
除数不能为0
上面的例子中,b=0,引发了ArithmeticException异常,因此创建了ArithmeticException对象,并由throw语句将异常抛给Java运行时系统,系统寻找匹配的异常处理器catch并运行相应的异常处理代码,打印输出”除数不能为0“。try-catch语句结束。
实际上,除数为0等于ArithmeticException,是RuntimeException的子类,运行时异常将由系统自动抛出,不需要使用throw语句,上面的代码等价于如下代码:
public class Example6_5 {
public static void main(String[] args) {
int a = 6;
int b = 0;
try {
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
}
- 例2:
public class Example6_5 {
public static boolean main() throws ArithmeticException {
int a = 6;
int b = 0;
try {
if (b == 0) {
throw new ArithmeticException();//通过throw抛出异常
}
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
throw e;//通过throw抛出异常
}finally {
return false;
}
}
public static void main(String[] args) {
try {
System.out.println(main());
}catch (Exception e){
System.out.println("捕获");
}
}
}
程序运行的结果为:
除数不能为0
false
可以看到finally语句块中的return会覆盖掉catch里面的throw抛出的异常
- 例3:
public class Example6_5 {
public static boolean main() throws ArithmeticException {
int a = 6;
int b = 0;
try {
if (b == 0) {
throw new ArithmeticException();//通过throw抛出异常
}
System.out.println(a / b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
return true;//通过throw抛出异常
}finally {
throw new ArithmeticException();
}
}
public static void main(String[] args) {
try {
System.out.println(main());
}catch (Exception e){
System.out.println("捕获");
}
}
}
可以猜测一下程序的运行结果:
除数不能为0
捕获
可以看到同样finally里面的throw会覆盖catch里面的return
(4)使用场景
- 针对不知道如何处理的异常,采用抛出的方式去处理。
- throw可抛出系统自定义异常,通常情况下,是用来抛出用户自定义的异常。
- throw后面不能跟其它代码块,否则编译不能通过,但是可以在finally语句块中有return语句,finally语句可以让throw和return共存
try {
System.out.println("除数不能为0")
}catch (ArithmeticException e){
throw new ArithmeticException();
System.out.println(a / b);//将会报错
}finally {
return false;
}
return true;//将会报错
四、自定义异常
1.定义
继承Throwable或者他的子类Exception的用户自己定义的异常类。前面的内容提到的都是系统有的异常类。
2.在程序中使用自定义异常的步骤
- 创建自定义异常类
- 在方法中通过throw抛出异常对象
- 如果在当前方法中对抛出的异常对象作处理,可以使用try-catch语句块捕获抛出异常对象并且处理,否则要在方法的声明处通过throws关键字指明要抛出给方法调用者的异常。
- 在出现异常方法的调用者中捕获并处理异常
3.结构语法
class UserException extends Exception {
UserException(){
super();
...//其他语句
}
}
throw关键字通常用在方法体中,并且抛出一个异常对象
4.举例
- 例1(该定义异常的方式不常用)
public class Example6_4 {
static int quotient(int x,int y) throws Exception{
if(y<0){
throw new MyException("除数不能是负数");
}
return x/y;//返回值
}
public static void main(String[] args) {
int a= 3;
int b=0;
try {
int result = quotient(a,b);//调用quotient自定义异常方法,当b=0,会抛出ArithmeticException类的异常对象
System.out.println(result);
}catch (MyException e){
System.out.println(e.getMessage());
}catch (ArithmeticException e) {//异常对象会被此ArithmeticException类捕获
System.out.println("除数不能为0");
}catch (Exception e){
System.out.println("程序发生了其它的异常");
}
}
}
/**
* 创建自定义异常
*/
class MyException extends Exception{
String message;
public MyException(String ErrorMessage){
this.message = ErrorMessage;
}
@Override
public String getMessage() {
return message;
}
}
输出结果是:
除数不能为0
- 例2 (该定义异常的方式常用)
- 创建自定义异常类AgeException,继承自类Exception。使用Throw关键字抛出异常对象。
input方法用于接收从键盘输入的姓名和年龄,如果输入为负数,则会抛出AgeException异常并积极捕获处理,如果输入正确,则会将姓名和年龄输出同时结束整个程序。
//定义异常类AgeException
import java.util.Scanner;
/**
* 1、java内置的异常类无法描述年龄的异常,所以需要自定义一个可以描述年龄的异常类
*/
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()) {
//Scanner类中的hasNext()方法可以判断下一个输入是否为整数,整数才会进入到循环体,该程序利用这个特点并且结合if判断语句来控制年龄的输入是否<0
try {
int age = scanner.nextInt();//接收键盘上年龄的输入
if (age < 0) {
throw new AgeException("年龄不能为负数");//2、一旦出现年龄输入为< 0的情况,就会通过throw抛出一个AgeException类的对象并且捕获处理
}
System.out.println("姓名" + name);
System.out.println("年龄" + age);
break;
} catch (AgeException e) {
System.out.println(e.getMessage() + "请重新输入:");
}
}
}
public static void main(String[] args) {
input();
}
}
输出结果是:
请输入姓名:
jack
请输入年龄:
-20
年龄不能为负数请重新输入:
-34
年龄不能为负数请重新输入:
-5
年龄不能为负数请重新输入:
11
姓名jack
年龄11
例3:
public class InterfaceException extends RuntimeException {
private Integer code;
private String msg;
public InterfaceException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return this.code;
}
public String getMsg() {
return this.msg;
}
public void setCode(final Integer code) {
this.code = code;
}
public void setMsg(final String msg) {
this.msg = msg;
}
}
@PostMapping(value = "/addSeniorMembers")
public Result addSeniorMembers(@RequestBody MembersList membersList) {
if (ObjectUtil.isNotEmpty(membersList)){
return flowRecordService.save(membersList);
}else {
throw new InterfaceException(1001,"参数错误");
}
}
注意:异常在方法内throw抛出后,需要在method上通过throws抛出
例4:可以在自定义异常时,定义不同的ResultType,给出不同的处理
class BaseException extends Exception { //异常基类
ResultType resultType;
public BaseException(ResultType resultType ) {
super(resultType.getMessage);
this.ResultType = resultType;
}
...
}
class SystemException extends BaseException { //自定义异常
public SystemException(ResultType resultType ) {
super(resultType);
}
}
interface ResultType{
string code;
string message;
}
class enum SystemResultType implments ResultType{
DATA_ISNULL("20001","数据为空")
...
private string code;
private string message;
}
5.系统定义的异常与用户定义的异常区别
系统定义的异常是特定情况出现的问题,而此时用来对可能遇到的问题进行处理。用户定义的异常是自己觉得可能会出现问题时,需要处理的。这样避免程序中断或是出现未知错误。
系统异常有两种一种是运行时异常,一种是普通异常,普通异常要求用户捕获或者抛出的,不捕获或者抛出就会编译不通过。运行时异常编译可以通过,但是运行时才显露出来。
五、Try-With-Resources
-
Java库中有很多资源需要手动关闭,例如打开的文件、连接的数据库等。在java7之前都是try-finally的方式关闭资源,try后面总是跟着一个“{”。
-
举个例子:try-finally方式关闭资源
package com.chapter6;
import java.io.*;
/**
* @Author: qp
* @Time: 2021/9/3 19:14
* @Description
*/
public class Example6_8 {
public static void main(String[] args) {
copy("E:/MyFirst.java","F:/First.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 Example6_9 {
public static void main(String[] args) {
copy("E:/MyFirst.java","F:/First.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();
}
}
}
六、异常的使用原则
Java异常强制用户去考虑程序的健壮性和安全性,异常处理不用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应的处理。编写代码处理某个异常时可遵循以下的一些原则:
- 在当前方法声明中使用try-catch捕获异常
- 一个方法被调用时,调用它的方法必须抛出相同的异常或异常的父类;
- 如果父类抛出多个异常,则覆盖方法必须抛出多个异常的一个子集,不能抛出新的异常
- 不要过度使用异常,不要使用异常代替流程控制,不要抛出简单异常代替所有错误
- 不要使用庞大的try语句,否则会增加编程的复杂度,可以把大块的try语句分成多个可能出现异常的程序段落,分别放在单独的try模块中
- 避免一个catch处理很多异常
- 不要忽略捕捉到的异常,对于捕捉到的异常尽可能在catch语句块解决
- 尽可能使用try-with-resources
七、三层架构异常处理
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());
}
}
4、SpringBoot项目全局异常处理:一般在使用@ControllerAdvice+@ExceptionHandler全局统一处理并捕获异常返回前端。
package com.ruoyi.common.security.handler;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.exception.DemoModeException;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.exception.auth.NotPermissionException;
import com.ruoyi.common.core.exception.auth.NotRoleException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
/**
* 全局异常处理器
*
* @author ruoyi
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限码异常
*/
@ExceptionHandler(NotPermissionException.class)
public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 角色权限异常
*/
@ExceptionHandler(NotRoleException.class)
public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 请求路径中缺少必需的路径变量
*/
@ExceptionHandler(MissingPathVariableException.class)
public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
* 请求参数类型不匹配
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 内部认证异常
*/
@ExceptionHandler(InnerAuthException.class)
public AjaxResult handleInnerAuthException(InnerAuthException e)
{
return AjaxResult.error(e.getMessage());
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult handleDemoModeException(DemoModeException e)
{
return AjaxResult.error("演示模式,不允许操作");
}
}