1、概述
Java 代码中的异常处理是非常重要的一环,从代码中可以看到,它的使用已经和业务逻辑紧密的结合在一起,部分业务逻辑还是依靠异常来完成的,更多的时候进行异常处理可以完善逻辑,避免可能的出错,规避小错误引发的大停顿。
在一般的项目之中,都会自定义运行时异常,用以适应项目的需要,这种异常可被捕捉,也可不被捕捉,它们不会导致整个系统挂掉,但是很多情况下,不捕捉处理就会导致业务出错。
在这里我们模拟几种情况,点明异常捕捉的使用时机。
2、情况分析
先来看没有任何处理的代码
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7 8
- }
- 9 10 public static void invoke() {
- 11 System.out.println("---11---");
- 12 int i = 1 / 0;
- 13 System.out.println("---12---");
- 14
- }
- 15
- }
其执行结果如下:
- ---1------11---Exception in thread "main"java.lang.ArithmeticException: / by zero
- at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
- at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)/
解析:main 方法调用 invoke 方法,在执行到第 12 行时出错,产生算法异常,此时由于无任何异常处理手段,结果就是,程序执行到这里之后直接中断,执行结果中输出的异常堆栈信息是 Java 内部默认的异常处理机制处理的结果。
改造一:我们在 invoke 方法内部加上异常捕捉机制,代码如下:
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7 8
- }
- 9 10 public static void invoke() {
- 11
- try {
- 12 System.out.println("---11---");
- 13 int i = 1 / 0;
- 14
- } catch(Exception e) {
- 15 System.out.println("---12---");
- 16
- }
- 17 System.out.println("---13---");
- 18
- }
- 19
- }
执行结果:
- ---1------11------12------13------2---
结果解析:我们在 invoke 方法的执行代码外围添加异常捕捉代码,捕捉 Exception 异常,这是所有异常的基类,当然也包含这里的算法异常,那么这个捕捉机制就会将 1/0 产生的异常捕捉到,捕捉到这个异常之后,就会跳转到 catch 语句块中执行针对这个异常的处理语句,执行完成后,会继续执行 try...catch 语句块之后的代码,这样的好处显而易见,一处的小错误并不会阻挡整个代码的持续执行,当然如果是严重问题,我们确实需要暂停执行的,就不能使用这种情况,使用之前的代码就行,所以异常处理机制的执行时机完全是由项目的业务情况而定的,是非常灵活的,不是固定的死板的。我们要根据时机的业务场景来合理的使用才是正理。
改造二:我们在 main 方法中也添加异常捕捉
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4
- try {
- 5 System.out.println("---1---");
- 6 invoke();
- 7
- } catch(Exception e) {
- 8 System.out.println("---2---");
- 9
- }
- 10 System.out.println("---3---");
- 11
- }
- 12 13 public static void invoke() {
- 14
- try {
- 15 System.out.println("---11---");
- 16 int i = 1 / 0;
- 17
- } catch(Exception e) {
- 18 System.out.println("---12---");
- 19
- }
- 20 System.out.println("---13---");
- 21
- }
- 22
- }
其执行结果如下:
- ---1------11------12------13------3---
结果几乎与之前的完全一致,不同之处在于 2 没有输出,一是我改变了 2 的输出位置,并新增了 3 的输出,现在 3 相当于之前 2 的位置,2 没有输出的原因是因为任何一个异常只能被捕捉一次,一旦被捕捉处理,那么之后就不会再次被捕捉,即使我在 main 方法中将异常类型改成算法异常,也不会捕捉到,异常只会被距离它最近的包含该异常的异常捕捉到,这里的两个异常捕捉其实就是一个嵌套的异常捕捉,而且二者捕捉的异常还是一致的,一般情况我们是不会这么使用的,因为毫无意义。但不是说它就完全不会出现,可能 invoke 中的代码较长,会有多处异常情况出现,我们可以在 main 方法中统一捕捉,而 invoke 中的异常捕捉只针对单一异常,表示这个异常的出现不会影响 invoke 方法后面的代码执行,没有异常捕捉的代码一旦出现异常就会中断其后方所有代码的执行(同一代码块内),这个异常会被 main 方法中的异常捕捉机制捕捉到并执行处理,这样 main 方法中调用 invoke 之后的代码仍然可以执行,不会被调用发生异常而中断。
但是如果我们再将 invoke 方法中的异常捕捉改变如下:
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4
- try {
- 5 System.out.println("---1---");
- 6 invoke();
- 7
- } catch(Exception e) {
- 8 System.out.println("---2---");
- 9
- }
- 10 System.out.println("---3---");
- 11
- }
- 12 13 public static void invoke() {
- 14
- try {
- 15 System.out.println("---11---");
- 16 int i = 1 / 0;
- 17
- } catch(NullPointerException e) {
- 18 System.out.println("---12---");
- 19
- }
- 20 System.out.println("---13---");
- 21
- }
- 22
- }
执行结果发生了变化:
- ---1------11------2------3---
为什么呢?正是因为我们更改了 invoke 方法中捕捉的异常类型,之前是异常基类型 Exception,现在改成具体的空指针异常,那么这个异常捕捉就只能捕捉空指针异常,它对此处发生的算法异常就会视而不见(由于异常类型的不对口,那么这个异常捕捉相当于没有添加,可以想象成没有异常捕捉的情况),这样就导致 invoke 方法中在 1/0 发生异常之后的所有代码全部不会执行,而我们在 main 方法中新增的异常捕获却能捕获到这种算法异常,所以 12 和 13 都不会输出,而是在异常发生后直接就跳转到 main 方法中进行异常捕捉,执行 catch 语句块处理语句输出 2,然后是 3。
在看一个特殊的情况:
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7
- }
- 8 9 public static void invoke() {
- 10
- for (int i = -2; i < 3; i++) {
- 11 System.out.println("---11---");
- 12 System.out.println("12/" + i + "=" + 12 / i);
- 13 System.out.println("---12---");
- 14
- }
- 15 System.out.println("---13---");
- 16
- }
- 17
- }
invoke 方法中是一个循环输出,当第 12 行发生异常时,循环中断,默认的异常处理机制打印异常堆栈:
- ---1------11---12 / -2 = -6---12------11---12 / -1 = -12---12------11---Exception in thread "main"java.lang.ArithmeticException: / by zero
- at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
- at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)/
改造一:在 invoke 方法的 for 循环外部添加 try...catch:
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7
- }
- 8 9 public static void invoke() {
- 10
- try {
- 11
- for (int i = -2; i < 3; i++) {
- 12 System.out.println("---11---");
- 13 System.out.println("12/" + i + "=" + 12 / i);
- 14 System.out.println("---12---");
- 15
- }
- 16 System.out.println("---13---");
- 17
- } catch(Exception e) {
- 18 System.out.println("---14---");
- 19
- }
- 20 System.out.println("---15---");
- 21
- }
- 22
- }
结果:
- ---1------11---12 / -2 = -6---12------11---12 / -1 = -12---12------11------14------15------2---
查看结果,发现循环还是中断了,当 i=0 时,第 13 行产生异常,之后循环中断,然后异常才会被 for 循环之外的异常捕捉到,这种场景也会在实际项目中出现,但不多见,具体场景为,针对循环进行异常捕捉,一旦循环中某一环产生异常,则整个循环终止,处理异常。
改造二:在循环体中加入 try...catch 块
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7
- }
- 8 9 public static void invoke() {
- 10
- for (int i = -2; i < 3; i++) {
- 11
- try {
- 12 System.out.println("---11---");
- 13 System.out.println("12/" + i + "=" + 12 / i);
- 14 System.out.println("---12---");
- 15
- } catch(Exception e) {
- 16 System.out.println("---13---");
- 17
- }
- 18 System.out.println("---14---");
- 19
- }
- 20 System.out.println("---15---");
- 21
- }
- 22
- }
执行结果:
- ---1------11---12 / -2 = -6---12------14------11---12 / -1 = -12---12------14------11------13------14------11---12 / 1 = 12---12------14------11---12 / 2 = 6---12------14------15------2---
这种情况比较多见,我们将异常捕捉内置到 for 循环内部,只针对循环体进行异常捕捉,这样当某一次循环体执行时产生了异常,也能私下处理好,不会影响整个循环的继续执行。在循环中还可以结合 continue 和 break 关键字进行更加复杂的关系控制,来达到特定的业务需求。
这里我来展示一种情况,也是刚刚做过的一个业务场景:
- 1 public class ExceptionTests01 {
- 2 3 public static void main(String[] args) {
- 4 System.out.println("---1---");
- 5 invoke();
- 6 System.out.println("---2---");
- 7
- }
- 8 9 public static void invoke() {
- 10
- for (int i = -2; i < 3; i++) {
- 11
- try {
- 12 System.out.println("---11---");
- 13 System.out.println("12/" + i + "=" + 12 / i);
- 14 System.out.println("---12---");
- 15
- } catch(Exception e) {
- 16 System.out.println("---13---");
- 17
- continue;
- 18
- }
- 19 System.out.println("---14---");
- 20
- }
- 21 System.out.println("---15---");
- 22
- }
- 23
- }
你没看错,只是添加了一个 continue; 控制信息的展示,当发生异常之后,执行 catch 块代码,输出 13 后,不再执行输出 14 的操作,而是直接开始新的循环。
有必要做个总结:
1 - 异常的捕捉是有方向性和类型针对性的,异常会被距离异常发生点最近的包含或者就是捕捉该类型异常的捕捉点捕捉到。这样我们在做嵌套异常捕捉和多异常捕捉时,就一定要注意要将小范围的异常类型放置到靠进 try 块的位置,避免大类型劫持异常,导致你设置的异常类型无法生效。
2 - 我们将一段代码 try...catch 包裹,就可以将这段代码从这个方法体中隔离出来,将其影响度降到最低,即使其发生异常,也不会影响到后续代码的执行。
3-throw 关键字的配合使用,我们可以在 catch 块中使用,表示将捕捉到的异常再次抛出,这里不做处理,这样就必须在方法的调用处再次进行捕捉,可以持续抛出,但是直到最终的方法时,必须要进行处理(对应明确抛出的异常一定要进行捕捉处理,不论你抛几次)。
来源: http://www.cnblogs.com/V1haoge/p/7191280.html