Java--异常详解

异常分类

如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。

image-20200811183913204

Throwable

Throwable 是 Java 语言中所有错误或异常的超类。

也就是说,如果你要抛出或者声明一个异常,那么,这个异常必须继承Throwable.

public class Main {

    public static void main(String[] args) {

    }


    public void testMyError() throws MyError{
        throw new MyError();
    }

    public void testMyErrorNon() throws MyErrorNon {
        throw new MyErrorNon();
    }

    public void testMyException() throws MyException {
        throw new MyException();
    }

    public void testMyExceptionNon() throws MyExceptionNon{
        throw new MyExceptionNon();
    }
}
image-20200811185444881

Error

Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止.

image-20200811185821287

Exception

image-20200811185851801

Exception 又 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一个是CheckedException.

RuntimeExceptionn

运行时异常

如 : NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常CheckedException,如 I/O 错误导致的 IOException、SQLException。 RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一定是程序员的错误.

CheckedException

检查异常

一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一
般包括几个方面:

  1. 试图在文件尾部读取数据
  2. 试图打开一个错误格式的 URL
  3. 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在

异常处理方式

throw

image-20200811190156250

throws

image

jvm自动抛出

image-20200811190226930
image-20200811190236707

throw和throws的异同

相同点:

  1. 都继承Throwable
  2. 程序的执行顺序收到影响
  3. 不一定每次都出现,概率不确定
  4. 消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常常有方法的调用者处理。

不同点:

  • 位置不同:
    1. ==throws用在方法上== ,后面跟的是异常类,可以跟多个;==而throw用在方法内==,后面跟的是异常对象。
  • 功能不同:
    1. throws用来声明异常,==让调用者知道该方法可能出现的问题==,可以给出预先的处理方式;==throw抛出具体的异常问题对象==,执行到throw,就无法在继续按正常的顺序继续执行了。跳转到调用者,并将具体的问题对象抛给调用者。
    2. throws表示==出现异常的一种可能性==,并不一定会发生这些异常;throw则是==抛出了异常==,执行throw则一定抛出了某种异常。

异常的捕获

image-20200811192641646
public class Main {

    public static void main(String[] args) {
        try {
            testcatch();
        } catch (MyException4 myException4) {
            myException4.printStackTrace();
        } catch (MyException3 myException3) {
            myException3.printStackTrace();
        } catch (MyException2 myException2) {
            myException2.printStackTrace();
        } catch (MyException1 myException1) {
            myException1.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    public static void testcatch() throws Throwable, MyException4, MyException3, MyException2, MyException1, Exception, Error {
    }

}

异常的捕获,最靠近try的,异常范围是最小的。异常匹配时,从上到下进行匹配,如果将范围大的异常捕获放在前面,那么后面比这个异常范围小的异常,永远得不到调用。

异常调用链

每个方法内部出现异常后,要么方法内部使用try捕获异常,方法内解决这个异常。使程序继续运行下去。

要么将异常抛出,由方法的调用者处理这个异常。

public class OMain {

    public static void main(String[] args) {
        try {
            new OMain().test4();
        } catch (MyException4 myException4) {
            myException4.printStackTrace();
        }
    }

    public void test4() throws MyException4 {
        try {
            test3();
        } catch (MyException3 myException3) {
            throw new MyException4();
        }
    }

    public void test3() throws MyException3 {
        try {
            test2();
        } catch (MyException2 myException2) {
            throw new MyException3();
        }
    }

    public void test2() throws MyException2 {
        try {
            test1();
        } catch (MyException1 myException1) {
            throw new MyException2();
        }
    }

    public void test1() throws MyException1 {
        try {
            test();
        } catch (Exception e) {
            throw new MyException1();
        }
    }

    public void test() throws Exception {
        throw new Exception();
    }

}

在这个例子中,真正的异常是在test方法中出现的,但是因为每一个方法都在自己的方法内部捕获了下层异常,然后抛出自己的异常。

此时整个异常已经失真了,从异常堆栈根本无法知道问题出在哪里:

image-20200811193704999

异常调用链的完整,可以帮助我们更快的定位问题。

public class OMain {

    public static void main(String[] args) {
        try {
            new OMain().test4();
        } catch (MyException4 myException4) {
            myException4.printStackTrace();
        }
    }

    public void test4() throws MyException4 {
        try {
            test3();
        } catch (MyException3 myException3) {
            MyException4 myException4 = new MyException4();
            myException4.initCause(myException3);
            throw myException4;
        }
    }

    public void test3() throws MyException3 {
        try {
            test2();
        } catch (MyException2 myException2) {
            MyException3 myException3 = new MyException3();
            myException3.initCause(myException2);
            throw myException3;
        }
    }

    public void test2() throws MyException2 {
        try {
            test1();
        } catch (MyException1 myException1) {
            MyException2 myException2 = new MyException2();
            myException2.initCause(myException1);
            throw myException2;
        }
    }

    public void test1() throws MyException1 {
        try {
            test();
        } catch (Exception e) {
            MyException1 myException1 = new MyException1();
            myException1.initCause(e);
            throw myException1;
        }
    }

    public void test() throws Exception {
        throw new Exception();
    }

}

在Throwable中,有一个属性cause,默认是自己。这个属性就是记录当前异常的上一个异常是谁。如果cause就是自己,那么,说明这个异常就是最先抛出的异常。

image-20200811194520023

这样整个异常调用链就完整了:

image-20200811194652221

其调用关系,可以完整的从堆栈中看出。

这是最常见的场景。

我们假设这个调用是上下级的调用:

test4->test3->test2->test1->test

其中越是下级,做的工作越简单,也越是底层方法的调用。在底层方法得到调用中,就会出现各种各样的异常(什么异常都有可能)

在例子中出现的是Exception。

在test方法中,可能完全不明白为什么出现异常,或者说,出现异常的原因是什么,如何解决?这些问题在test方法中都无法解决的。

于是test方法将自己无法解决的异常抛出。

在test1方法中,捕获到了异常,可能test1交给test是读取一个文件,结果出现了文件没找到的异常。那么,在test1方法中就知道了,是文件没找到,但是,对于没找到的文件,该如何处理呢?test1就不知道了,于是,继续向上抛出异常,此时异常被进行细化,由文件没找到的异常转换为文件读取失败。

在test2方法中,捕获到了异常,可能test2交给test1是读取一个文件夹的文件,结果出现了部分文件未找到。那么在test2方法中,我们知道了,是部分文件没找到,而不是文件夹内全部的文件没找到。在test2方法中,可以确定,是某一个文件没有找到,那么对于这个文件,我们是要忽略,还是重试呢?test2也不知道,此时只能转换异常,然后继续抛出。转换的异常更加细化,部分文件未找到。

。。。。

以此类推,最终异常被交给了主线程,当主线程也无法确认后,将问题抛给用户,由用户决定。用户决定如何处理。

这个过程可以类比我们使用操作系统复制整个文件夹的过程。

里面是一层层的划分,当出现无法抉择的异常,最终抛给用户决定。

遇到可以抉择的异常,直接处理。(一个文件失败自动重试3次。。)

将异常交由调用者处理,是非常正确的处理方式。\

==这种方式是金字塔结构,越是底层的方法,异常范围越大。==

==还有一种是倒金字塔结构,越是底层的方法,异常越小。==对于这种结构,无需做任何捕获,直接抛出即可。

public class OMain {

    public static void main(String[] args) {
        try {
            new OMain().test();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public void test4() throws MyException4 {
        throw new MyException4();
    }

    public void test3() throws MyException3 {
        test4();
    }

    public void test2() throws MyException2 {
            test3();
    }

    public void test1() throws MyException1 {
            test2();
    }

    public void test() throws Throwable {
            test1();
    }

}
image-20200811200302026

你可能感兴趣的:(Java--异常详解)