Java异常机制

世界上不可能存在不会出错的程序,对于一门优秀的语言,它必定具备容灾机制的特性,这样才能保证程序的可靠性。

Java在设计之初就提供了相对完善的异常处理机制,该机制大大降低了编写和维护可靠程序的门槛,或许这也是Java如今这么火爆的原因之一。

在此,先抛出两个面试中常问到的问题——Exception和Error的区别,RuntimeException和CheckedException的区别

 

Exception和Error的区别

Exception和Error都继承自Throwable类,在Java中只有继承了Throwable类型的实例才能够被抛出或捕获。

Exception和Error体现了Java平台设计者对不同异常情况的分类:

  • Exception:是程序正常运行中可以预料的异常,它可以且应该尽可能被捕获并处理。
  • Error:是正常情况下不大可能出现的系统错误,一般Error都会导致程序(如JVM本身)处于非正常的,不可恢复的状态。既然是非正常的情况,不便于也不需要捕获,则编译器不会对Error作检查。常见Error有虚拟机崩溃,内存不足,方法栈调用异常等。

直观地说:Error是程序无法处理的错误,Exception是程序可以处理的异常。

 

RuntimeException和CheckedException的区别

而Exception又分为CheckedException和RuntimeException:

  • CheckedException可检查异常:是可预知的异常,在编译期就必须显示地对其进行捕获处理,例如FileNotFoundException,ClassNotFoundException等。
  • RuntimeException不检查异常:也就是我们常说的运行时异常,它是不可预知的,具体根据需求来判断是否需要捕获,并不会在编译期强制要求(大部分情况下我们都是采用if-else判断,因为try-catch块会对性能方面存在一点影响)RuntimeException通常是可以编码避免的逻辑错误,例如NullPointerException、ArrayIndexOutOfBoundsException等。

 

以下是几个常见的RuntimeException,CheckedException,Error,希望自己和读者都能够做到心里有数...(゜▽゜*)

RuntimeException:

①NullPointException - 空指针异常

②ClassCastException - 类型强制转换异常

③IllegalArgumentException - 传递非法参数异常

④IndexOutOfBoundsException - 数组越界

⑤NumberFormatException - 数字格式异常

 

CheckedException:

①ClassNotFoundException - 找不到指定class的异常

②IOException - IO操作异常

 

Error

①NoClassDefFoundError - 找不到class定义的错误

②StackOverflowError - 深递归导致栈被耗尽的错误

③OutOfMemoryError - 内存溢出错误

 

到此,再抛出一个问题:ClassNotFoundException和NoClassDefFoundError有什么区别?

ClassNotFoundException和NoClassDefFoundError的区别

首先,一个是Error一个是CheckedException。既然NoClassDefFoundError是Error的话,那问题多半是与JVM挂钩了,我们先观察下面这个非常简单的代码:

可以看到,在TestNoClassDefFoundError中调用了A的构造函数创建了一个A对象,

public class TestNoClassDefFoundError {
    public static void main(String[] args) {
        new A();
    }
}

class A{
}

在我们点击执行main方法后,javac首先会通过我们编写的TestNoClassDefFoundError.java编译出两个.class文件(A.class和TestNoClassDefFoundError.class),然后就是JVM对我们的.class文件进行类加载了...... 也就是说,当main成功执行完毕后,我们可以获得TestNoClassDefFoundError.java和A.java这两个字节码文件。

此时我们将A.class删除,再点击main方法执行,就会报NoClassDefFoundError,也就是说JVM加载不到A.class去执行main中A的构造方法。

 

说完了NoClassDefFoundError,ClassNotFoundException就简单很多了,它是一个可检查异常,我们必须在编译期就得对它进行捕获。例如我们通过Class获取一个xx类的反射对象时,就需要抛出或捕获处理该异常,话不多还是说看代码吧:

public class Demo {
    public static void main(String[] args) {//throws ClassNotFoundException 
        path = "com.xxx.xxxClass";
        try {
            claz = Class.forName(path);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
        

读到这里相信大家对NoClassDefFoundError,ClassNotFoundException的区别已经有了大概的了解了( •̀ ω •́ )

 

说完了各种异常,那么再来看一下Java对这些异常的处理机制

处理机制

 

首先呢,是抛出异常:当一个方法出现错误引发异常时,方法会创建一个异常对象交付给运行时系统,该异常对象包含了异常类型和异常出现时的程序状态等异常信息。

之后就是捕获异常:在方法抛出异常后,运行时系统会去寻找合适的异常的异常处理器(ExceptionHandler),并执行其方法。若运行时系统遍历了调用栈都没有找到合适的异常处理器,则运行时系统终止,Java程序终止。

 

上述描述若没有看懂也没有关系,以下通过一段简单的代码对其过程进行描述:

public class ExceptionHandlerMechanism {
    public static int doMethod() {
        try {
            int i = 10/0;//抛出一个异常对象
            System.out.println(i);
        }catch (ArithmeticException e){//在catch块中寻找合适的异常处理器
            System.out.println("ArithmeticException");
            return 10;
        }catch (Exception e){
            System.out.println("Exception");
            return 5;
        }finally {
            System.out.println("finally");
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.print(doMethod());
    }
}

其运行过程如下:

①程序首先在int i = 10/0;的位置抛出一个异常对象

②然后运行时系统对该异常对象进行捕获操作,也就是上述过程中的寻找合适的异常的异常处理器

③该异常对象在ArithmeticException的catch块中被捕获,并执行其中的方法。

这里注意!!!ArithmeticException的catch块中的return是不会执行的,有一种错误的说法是:ArithmeticException先执行了return,然后finnally中执行的return会把先前ArithmeticException中return的结果覆盖。因为我曾经就这么认为了很久....(  ′⌒`)

④然后就是执行finally了。

其运行结果相信大家已经心里有数了:

ArithmeticException
finally
0

 

说完了异常的处理机制后,我们再来聊一聊异常的规范吧

规范

①不要捕获我们不需要捕获的异常:不必要的异常的捕获和处理交给掌握更多信息的上层,说白了就是别多闲事

不要生吞异常:捕获了异常则必须对其进行处理,不要什么都做,生吞异常会导致程序出现问题后难以诊断问题所在,也就是别占着茅坑不拉屎。

尽量不要捕获通用异常:能定位到FileNotFoundException就不要用IOException或Exception,这样能更加精确地定位问题的所在以及增加代码的可读性。此外,我们某些情况是需要将例如RuntimeException的异常扩散出来,错误的做法是选择捕获Exception,它会把我们不想捕获的RuntimeException也捕获了。

④对于很多子模块系统或分布式系统,采用日志持久化保存异常信息:在这类系统中,如果仅仅通过控制台打印异常,是无法找到相关异常信息的。

⑤抛出的异常能够通过其名称和message准确说明异常产生的类型和原因:这个就不用解释了

⑥仅捕获可能出现异常的必要的代码段:不要用一大个try包住整个代码段,因为try-catch的效率是远远比不上if-else/switch的。(因为try-catch块中每次遇到异常都会实例化一个Exception对象,对当时的栈进行快照进行保存等。)

 


关于异常差不多就写到这⑧..(●'◡'●)

你可能感兴趣的:(Java)