(一)概念
1.异常的产生
异常就是程序中发生的所有不正常的现象,类似于人会生病是一样的道理,异常是Java的重大特色,合理地使用异常处理,可以让我们程序更加健壮.
异常是导致程序中断执行的一种指令流,异常一旦出现并且没有进行合理的处理的话,那么程序就将中断执行.
一旦产生异常之后发现产生异常的语句以及后面的语句将不再执行,默认情况下是进行异常信息的输出,而后自动结束程序的执行.
程序员要做的事情是,即使出现了异常.那么也应该让程序正确的执行完毕.好的程序是在它出现错之后我还是希望它能够正常的执行完毕.
(二)处理异常
1.tryCatch异常处理的格式
如果要想进行异常的处理,在Java之中提供了三个关键字:try ,catch ,finally,而这三个关键字的使用语法如下:
try{
//有可能出现异常的语句
}[ catch (异常类型 对象)] {
//异常处理;
}[ catch (异常类型 对象)] {
//异常处理;
}[ catch (异常类型 对象)] {
//异常处理;
}[finally{
//不管是否出现异常,都执行的统一代码
}]
1、try代码块:监视代码块的执行,发现对应的的异常则跳转至catch,若无catch则直接到finally块。 2、catch代码块:用于处理try捕获到的异常,要么处理,要么向上抛出。
3、finally代码块:不管是否有异常,都必执行,一般用来清理资源,释放连接等。然而有以下几种情况不会执行到这里的代码:
程序所在的线程死亡。
关闭CPU。
代码执行流程未进入try代码块。
代码在try代码块中发生死循环、死锁等状态。
在try代码块中执行了System.exit()操作。
对于以后的操作组合:
1 try…catch
2 try…catch…finally
3 try…final (不建议出现,语法不适合开发)
出现异常的目的是为了解决异常,为了能够进行异常的处理,可以使用异常类中提供的printStackTrace()方法进行异常信息的完整输出
/**
* 1. 如果没输入参数报异常.
* 2. 用户输入的参数不是数字.
* 3. 被除数为零
*
* @param args
*/
public static void main(String[] args) {
System.out.println("1,除法计算开始");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2.除法计算");
int a = (x / y); // 这里出现了异常了(它后面的语句都不执行了 try代码块里面的)
System.out.println("看看这个语句是否还打印."); // 不执行
} catch (ArithmeticException e) {//捕获运算异常
// System.out.println("出现异常了"); //描述信息不完整
e.printStackTrace(); //打印了异常的信息很完整(出了什么异常,告诉你在第几行代码出现的异常)
} catch (ArrayIndexOutOfBoundsException e) {//捕第二个异常
e.printStackTrace(); //打印异常信息
} finally {
System.out.println("!!!!不管是否出现异常,我都要完整的执行");//即使出现异常 也会出现这行代码
}
System.out.println("3.除法计算结束了");//异常都处理完了,这个也会出现了
}
异常捕获了程序很健壮,但是以上的异常都已经知道了,你还让它出现,这个绝对就是技术问题了.正常情况下应该用if else来及时判断来给与提示
2.try异常返回值问题
如果try语句里有return,返回的是try语句块中变量值。 详细执行过程如下:
如果有返回值,就把返回值保存到局部变量中;
执行jsr指令跳到finally语句里执行;
执行完finally语句后,返回之前保存在局部变量表里的值。
如果try,finally语句里均有return,忽略try的return,而使用finally的return.
3.异常的处理流程
异常类的继承结构
经过异常类的观察可以发现所有的异常类都是Throwable的子类,而在Throwable下有两个子类
1. 程序在执行过程中发生了异常之后,会由JVM自动根据异常的类型产生实例化一个与之类型匹配的异常类对象
比如是算术出错,就是算术异常, 如果是数组越界就是数组越界异常
2. 产生异常之后会先判断当前的语句上是不是有异常处理(try捕获),如果没有异常处理,那么就交给JVM进行默认的异常处理
jvm默认的异常处理是,输出以后信息,然后结束程序的调用(后面的代码都不执行了)
3. 如果此时存在有异常的捕获(try捕获),那么会由try语句来捕获产生的异常类实例化对象,它抓住这个对象,然后与try语句后的每一个catch进行比较,如果现在有符合捕获类型,则使用当前的catch的语句来进行异常的处理,如果不匹配,则向下继续匹配其它的catch
4. 不管最后的异常处理是否能够匹配,都要向后执行,如果此时程序中存在有finally语句,那么就先执行finally中的代码,但是执行完毕后需要根据之前的catch匹配结果来决定如何执行,如果之前已经成功的捕获了异常,那么就继续执行finally之后的代码,如果之前没有成功的捕获异常,那么就将此异常交给jvm进行默认处理(输出异常信息,而后结束程序的执行)
异常处理整个过程就好比方法重载一样,根据catch后面的参数类型进行匹配,但是所有的Java对象都存在有自动的向上转型的操作支持,也就是说如果要真的匹配类型,简单的做法就是匹配Excepton 就够了,但是在编写捕获多个异常的时候,捕获的范围大的异常一定要放在捕获范围小的异常的后面.否则程序编译错误(语法错误)
虽然使用exception捕获了很方便,但是这样也很不好,因为所有的异常都会按照同样的一种方式进行处理,如果在一些要求严格的项目中,异常一定要分开处理更好.
4.throws关键字和throw关键字
throws关键字
throws关键字主要用于方法声明上,指的是当方法之中出现异常后交由被调用处来处理,调用了使用throws声明的方法之后,不管操作是否出现了异常,都必须使用try…catch来进行我们的异常的处理,如果在main方法继续抛出异常,那么这个异常就将交给jvm进行,也就是采用了我们默认的处理方式,输出异常信息,而后结束程序的调用.
主方法不要加上throws,因为程序如果出错了,也希望正常的结束程序的调用,如果交给jvm调用就不会正常的结束方法的调用.
public class lmxi {
//声明异常
public static int div(int x, int y) throws Exception {
return x / y;
}
/**
* 告诉调用者去处理
*/
@Test
public void ceui1() {
try {
lmxi.div(1, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
throw与throws的区别:
Throw:
强调抛出异常的动作,用于方法体之中,后面跟的是异常对象;
Throws:
强调的是可能会出现的某种问题,让调用者知道,然后给出一个解决方案;用于方法的声明上,后面跟的是异常类类名;
throw关键字
在程序之中可以直接使用throw手工的抛出一个异常类的实例化对象.
throw与throws的区别[面试题]
Throw:
在方法之中人为的抛出一个异常类对象(这个异常类对象可能是自己实例化或者抛出已存在的)
Throws:
在方法的声明上使用,表示的是此方法在调用时必须处理异常,如果出现就处理,如果不出现就正常执行
5.请解释error和exception的区别[面试题]
- Error : 指的是JVM错误,即:此时的程序还没有执行,如果没有执行用户无法处理,比如,错误,找不到或者无法加载主类(编译的时候无法找到这个类)
2. Exception: 指的是程序运行中产生的异常,用户可以处理
也就是所谓的异常处理指的就是所有Exception以及它的子类异常,我们才能进行处理
6.异常的执行顺序
https://www.yuque.com/docs/share/6a4d9ae3-9ffc-4780-9ed9-f9bdd04909d2?#
(三)其它
1.Throwable类介绍
Throwable是所有异常的父类Throwable类下面有两个常见子类:
1:Error
错误,非常严重的问题,通常这类问题,大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题
例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述
2:Exception
这个类型的问题,称为编译时期的异常,在代码编写的时候,问题就会暴露出来,程序员必须给出相应的解决方案,方可代码编译通过;
Exception则细分为两类,受检异常(check)需要我们手动try/catch或者在方法定义中throws,编译器在编译的时候会检查其合法性。非受检异常(uncheck)则不需要我们提前处理。2.Throwable类常用方法
public string getMessage():返回异常发生时的详细信息
public string toString():返回异常发生时的简要描述
public string getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
public void printStackTrace():在控制台上打印Throwable对象封装的异常信息
3.什么是编译时期和运行时期异常区别
Java中的异常被分为两大类:编译时异常和运行时异常。
所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常
编译时异常
Java程序必须显示处理,否则程序就会发生错误,无法通过编译
运行时异常
无需显示处理,也可以和编译时异常一样处理
4.异常处理最佳实践
好了,前面简单介绍了异常的分类以及try/catch/finally的注意事项,现在可以总结一下我们在异常处理的时候有哪些”最佳实践“了。
当需要向上抛出异常的时候,需根据当前业务场景定义具有业务含义的异常,优先使用行业内定义的异常或者团队内部定义好的。例如在使用dubbo进行远程服务调用超时的时候会抛出DubboTimeoutException,而不是直接把RuntimeException抛出。
请勿在finally代码块中使用return语句,避免返回值的判断变得复杂。
捕获异常具体的子类,而不是Exception,更不是throwable。这样会捕获所有的错误,包括JVM抛出的无法处理的严重错误。
切记更别忽视任何一个异常(catch住了不做任何处理),即使现在能确保不影响逻辑的正常运行,但是对于将来谁都无法保证代码会如何改动,别给自己挖坑。
不要使用异常当作控制流程来使用,这是一个很奇葩也很影响性能的做法。
清理资源,释放连接等操作一定要放在finally代码块中,防止内存泄漏,如果finally块处理的逻辑比较多且模块化,我们可以封装成工具方法调用,代码会比较简洁。
5.慎用异常
异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
(四)异常的使用
1.自定义异常
自定义异常概述:
当我们需要一个异常类描述一个java中没有描述的问题时,那么就需要我们自定义一个描述问题的类,这个类就是自定义异常类;
自定义异常是继承exception就可以了
自定义异常类必须继承java中已经存在的那些异常类中的其中一个;
继承的时候,可以继承Exception也可以继承RuntimeException;
如果继承Exception:
那么此时的自定义异常类就是一个编译时期的异常类;如果在方法中出现这个类型的问题,那么就必须给出相应的解决方案;
如果继承RuntimeException
那么此时的自定义异常类就是一个运行时期的异常类;如果在方法中出现这个类型的问题,那么就可以处理也可以不处理;
自定义异常步骤
1. 创建自定义异常类
2. 在方法中通过throw关键字抛出异常对象
3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理,否则在方法的声明处通过throws关键字指明要抛给方法调用者的异常,继续下一步的操作
4. 在出现异常的方法的调用者中捕获并处理异常public class MyException extends Exception {//创建自定义异常,继承Exception类
public MyException(String ErrorMessagr) { // 构造方法
super(ErrorMessagr); // 父类构造方法
}
}
字符串ErrorMessage是要输出的错误信息,若想抛出用户自定义的异常对象,要使用throw关键字
在项目中创建类Tran,该类中创建一个带有int型参数的方法avg(),该方法用来检查参数是否小于0或大于100,如果参数小于0或大于100,则通过throw关键字抛出一个MyException异常对象,并在main()方法中捕捉该对象.
public class Tran { // 创建类
// 定义方法,抛出异常
static int avg(int number1, int number2) throws MyException {
if (number1 < 0 || number2 < 0) { // 判断方法中参数是否满足指定条件
throw new MyException("不可以使用负数"); // 错误信息
}
if (number1 > 100 || number2 > 100) { // 判断方法中参数是否满足指定条件
throw new MyException("数值太大了"); // 错误信息
}
return (number1 + number2) / 2; // 将参数的平均值返回
}
public static void main(String[] args) { // 主方法
try { // try代码块处理可能出现异常的代码
int result = avg(102, 150); // 调用avg()方法
System.out.println(result); // 将avg()方法的返回值输出
} catch (MyException e) {
System.out.println(e); // 输出异常信息
}
}
}
2.异常demo代码
3.异常工具类与使用
注意不要用 Lombok的 @Data注解, 会有问题.
效果
postman显示内容
4.不在控制台打印堆栈信息
自定义异常打印堆栈信息很烦,没有太多的用处
解决办法,在 自定义异常类 中重写空的fillInStackTrace方法
/
重写这个方法目的是不让在控制台打印堆栈信息
/
@Override
public Throwable fillInStackTrace() {
return this**;
}
效果: