Java中CheckedException(受检异常)和UncheckedException(非受检异常)的区别

一、什么是异常?异常与Bug的区别是什么?

异常是指程序在运行过程中由于外部问题导致的程序运行异常的事件,异常的发生往往会中断程序的运行。异常在程序中非常常见,比如数组越界,除0异常,文件路径错误等等。

值得一提的是,异常和Bug二者是不能混为一谈的

异常是可以预测到的,但是异常不可避免(比如除0错误,这是无法避免的)。而Bug是程序的代码漏洞,所以Bug属于人为因素导致的,我们无法预测Bug的存在,只有在程序运行结果不符合预期的时候我们才会知道有Bug,但是Bug是可以避免的(比如避免使用野指针等等,只要程序员遵循良好代码规范,就可以避免这样的Bug出现)。

不过,无论是异常还是Bug,都是程序在运行阶段出现的。而异常可以通过异常处理机制进行处理,从而保证程序其他部分不受影响(比如服务器一个节点出现问题之后不能让整个系统都宕机,而是应该让出问题的节点停下,其他部分保持正常工作)。Bug所带来的结果是无法预测的,一旦出现Bug我们就需要重新分析代码,找到根源后对源代码进行修改。

二、异常的种类

异常的种类有很多。

比如说,Java代码中少了一个分号,那么运行出来结果是提示错误 java.lang.Error;或者是System.out.println(1/0),那么因为你进行了除0操作,会抛出 java.lang.ArithmeticException 的异常。

异常发生的原因有很多,通常包含以下几大类:

  • 受检异常(CheckedException):又称编译时异常,最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。(受检异常又叫编译时异常的原因就是因为它在编译时不能被忽略,程序必须对它有相应的处理。但是需要注意的是,无论是受检异常还是非受检异常,都是发生在程序的运行阶段,不要错误地认为编译时异常就发生在编译阶段!!!)
  • 非受检异常(UncheckedException):又称运行时异常, 运行时异常是可能被程序员避免的异常。与受检异常相反,运行时异常可以在编译时被忽略。

  • 错误(Error): 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

三、异常类的层次 

Java中CheckedException(受检异常)和UncheckedException(非受检异常)的区别_第1张图片

 java中有一个Throwable类(可抛出类),由它派生出了Error和Exception。

这里引用《深入理解Java:核心技术与最佳实践》中的一段话:

        非受检异常指的是java.lang.RuntimeException和java.lang.Error类及其子类,所有其他的异常类都称为受检异常。两种类型的异常在作用上并没有差别,唯一的差别就在于使用受检异常时的合法性要在编译时刻由编译器来检查。正因为如此,受检异常在使用的时候需要比非受检异常更多的代码来避免编译错误。

        ......

        目前主流的意见时,最好优先使用非受检异常。

这里可以看到,它把错误划入了非受检异常中。(虽然前面已经说了错误不是异常),这里就是这么规定的,记住就好了。

四、受检异常 

再一次强调:无论是受检异常还是非受检异常都发生在程序运行阶段

为什么前面说受检异常会在编译阶段被编译器检查,而这里又说受检异常发生在程序运行阶段呢?

仔细想一下就知道二者并不冲突,我将”发生“这个字标红以突出它。一个程序想要发生异常,那它一定要先跑起来,如果没跑起来就不可能发生异常。而编译器检查报错,是说如果被调用者throws了一个受检异常,那么调用者必须进行相应的异常处理,否则编译器就会报错。而这仅仅只是一个保护机制,只是提醒程序员不要忘记对这些异常进行处理,但实际上它们还没有真正发生。

受检异常的处理方法

受检异常的处理方法有两种:1.throws抛给上一级处理,2.使用try/catch进行捕捉

举一个例子:你是一个公司的职员,有一天因为你的一个小失误导致公司损失了50元,这时候你有两种处理方法:一是你自己拿出50元补上这个损失,二是你将这个损失上报给你的上一级领导,希望他能够处理。你的领导接收到这个消息后,他也有两种处理方法,一种就是他认为这50块对他来说不是事,就自掏腰包解决了。另一种就是他不想处理,于是继续上报给他的上一级进行处理。

1、throws关键字

看下面这个例子:

    public static void main(String[] args){
        f3();    //调用一个发生异常的方法
    }

    public static void f3(){
        System.out.println("f3 start");
        System.out.println(1/0);        //制造一个除0异常(受检异常)
        System.out.println("f3 end");
    }

如果编译这段代码,会发现编译器会编译通过,也就是说编译阶段不会出错。前面不是已经说了除0异常属于受检异常,调用者如果不处理就会报错吗?

注意,编译器帮你检查受检异常的前提条件是:被调用者throws受检异常

上面这段代码编译确实不会出错,但在运行的时候就会抛出ArithmeticException异常。

而在f3后面加上throws关键字后:

    public static void f3() throws Exception{
        System.out.println("f3 start");
        System.out.println(1/0);
        System.out.println("f3 end");
    }

main方法中立马就会报错,这就是编译器检查到你没有对f3进行异常处理。

Java中CheckedException(受检异常)和UncheckedException(非受检异常)的区别_第2张图片

可以看到在IDEA里它会自动生成两种方案。

如果选择第一个,他就会将代码改成如下的样子:

Java中CheckedException(受检异常)和UncheckedException(非受检异常)的区别_第3张图片

发现它在main方法后加上了throws关键字。这就是说,如果main方法里出现异常,他也会抛给上一级进行处理。main函数的上级就是JVM,而JVM处理异常一般就是终止程序。

2、try/catch结构

如果选择第二个,它就会将代码改成如下的结构:

Java中CheckedException(受检异常)和UncheckedException(非受检异常)的区别_第4张图片 

这就是将这段代码改为了try/catch结构对异常进行捕捉,如果捕捉到catch指定类型的异常,那么就会执行catch 内的代码。

另外一个try可以跟多个catch。一个catch里也可以通过”|“符号连接,指定多种异常。

当一个try后面跟多个catch的时候,从上到下应该是由精确类型的异常到大类型的异常。(精确类型的异常就是类似ArithmeticExceptionIndexOutOfBoundsException这种,具有精确意义的异常类。而他们的父类Exception就是大类型的异常,涵盖的面很宽,但是不精确)因为如果大类型的异常在上,它就会与后续的精确类型的异常处理程序发生冲突,java无法判断到底执行哪一个,所以规范的写法就是小类型在上大类型在下。

五、​​​​​​​非受检异常

​​​​​​​Error 和 RuntimeException 以及他们的子类。Java语言在编译时,不会提示和发现这样的异常,不要求在程序中处理这些异常。所以我们可以在程序中编写代码来处理(使用try…catch…finally)这样的异常,也可以不做任何处理。对于这些错误或异常,我们应该修正代码,而不是去通过异常处理器处理。这样的异常发生的原因多半是由于我们的代码逻辑出现了问题。

另外​​​​​​​,用户也可以自己写异常类。但是一般情况下不要自定义受检异常。

你可能感兴趣的:(java,开发语言)