Bootstrap

Java中的异常总结详解(异常类型、声明异常、抛出异常、捕获异常)

前言

    异常处理使得程序可以处理非预期的情景,并且继续正常的处理。
    在程序运行过程中,如果 JVM 检测出一个不可能执行的操作,就会出现运行时错误(runtime error)。例如,如果使用一个越界的下标访问数组,程序就会产生一个ArraylndexOutOfBoundsException 的运行时错误。如果程序需要输入一个double 值,会得到一个 InputMismatchException 的运行时错误。
    在 Java 中,运行时错误会作为异常抛出。异常就是一种对象,表示阻止正常进行程序执行的错误或者情况。如果异常没有被处理,那么程序将会非正常终止。该如何处理这个异常,以使程序可以继续运行或者优雅终止呢?本篇文章将介绍该主题。

一、异常概述

    异常就是程序在运行过程中出现的一些错误,我们通过面向对象的思想,把这些错误也用类来描述,那么一旦产生一个错误,即就是创建了某一个错误的对象,这个对象就是我们所说的异常对象。
    在这之前也见过很多的异常错误,例如:
(1)IndexOutOfBoundsException:

  • ArrayIndexOutOfBoundsException
    数组角标越界异常 角标不在数组范围内

  • StringfIndexOutOfBoundsException
    字符串角标越界异常 角标不在字符串范围内

(2)NullPointerException
    空指针异常 对null调用其成员。
(3)ArithmeticException
    数学运算异常 非法的数学运算。
(4)ClassCastException
    类型转换异常 将类进行错误的强制转换。
(5)NumberFormatException
    数字格式化异常 将数字字符串进行解析。
(6)InputMismatchException
    输入不匹配异常 在输入时输入非法数据。
(7)ParseException
    时间解析异常 非法的时间格式。
(8)StackOverFlowError
    栈内存溢出异常 函数递归。
(9)OutOfMemoryError
    堆内存异常 数组空间开辟过大 程序中对象太多。

    为了演示异常处理,在这里举了个例子,进行讲解:

 public static void main(String[] args) {
    int[] arr = {1,2,3,4,5};
    int num = getNumber(arr,10);
    System.out.println(num);
}
private static int getNumber(int[] arr, int i) {
    return arr[i];
    //如果arr为null NullPointerException
    //如果i越界 ArrayIndexOutOfBoundsException
}

    如上述代码所示,对于一个有返回值的函数而言,如果操作计算是正确的话,肯定会有一个正常的返回值,如果操作计算是错误的话,从函数的角度而言 会不会有正常的返回值呢?不会;
    本质上,大部分的错误,其实都是由JVM发现并抛出的 当然我们也可以手动去判断。

private static int getNumber(int[] arr, int i) {
    //return arr[i];//是由JVM来进行判断的
    //模拟JVM的操作 用手动的判断模拟JVM自动的发现
    if (arr == null) {
        //产生一个空指针异常的问题对象
        //用throw(抛出)关键 将产生的问题告知调用者
        throw new NullPointerException();
        //一旦上述抛出一个问题 此函数立马中断
        //类似于return 正常结束(弹栈)
        //throw非正常结束(中断 强制弹栈 并抛出问题给调用者)
    }
    if (i < 0 || i >= arr.length) {
        //产生一个数组角标越界异常的问题对象
        throw new ArrayIndexOutOfBoundsException();
        //throw new StackOverflowError();
        //PS 一旦出现错误 抛出问题即可 但是最好抛出最相关最精确的问题
    }
    return arr[i];
}

    getNumber函数如果发生了问题是直接将问题抛给主函数的,但是主函数也没有处理这个问题,主函数接着将问题抛给调用者JVM,JVM才不帮你解决 结果就是程序中断了!
    JVM----->调用main----->调用getNumber----->getNumber出现异常----->抛出给main----->main没有处理----->抛出传递给JVM----->JVM直接中断。
    PS:当然 函数内部如果出现问题,也可以将问题在内部处理 处理后 就不需要向上层抛出。

二、异常类型

    既然错误的原因有很多,描述错误的类也有很多,那么这种情况就产生了一个大的家族/体系,这里面全都是异常错误类,我们叫做异常体系。
    对于异常体系而言,大家都是错误,只不过所产生的原因不太一样,不断的进行向上抽取共性,最终抽出Throwable这个接口(可以被抛出的),但凡是异常对象,无论原因,都可以被抛出。
    Throwable类是 Java 语言中所有错误或异常的超类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
    异常是对象,而对象都采用类来定义。异常的根类是 java.lang.Throwable。关系图如下:
在这里插入图片描述
    注意:类名 Error、Exception 和 RuntimeException 有时候容易引起混淆。这三种类都是异常,这里讨论的错误都发生在运行时。
    Throwable 类是所有异常类的根。所有的 Java 异常类都直接或者间接地继承自Throwable。可以通过继承 Exception 或者 Exception 的子类来创建自己的异常类。
    这些异常类可以分为三种主要类型:系统错误、编译时异常和运行时异常。

1.系统错误(Error)

  • 系统錯误(system error) 是由 Java 虚拟机抛出的,用 Error 类表示。Error 类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
    在这里插入图片描述

OutOfMemoryError :内存耗尽 ;
NoClassDefFoundError :无法加载某个Class ;
StackOverflowError :栈溢出。

2.编译时异常(Exception)

  • 编译时异常:Exception及其子类(除了RuntimeException),在编译时期抛出的异常,在编译期间检查程序是否可能会出现问题,如果可能会有,则预先防范:捕获 声明。
    在这里插入图片描述

Exception 则是编译时异常,它可以被捕获并处理。
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:

  • NumberFormatException :数值类型的格式错误;
  • FileNotFoundException :未找到文件;
  • SocketException :读取网络失败。

还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:

  • NullPointerException :对某个 null 的对象调用方法或字段;
  • IndexOutOfBoundsException :数组索引越界。

3.运行时异常(RuntimeException)

  • 运行时异常(runtime exception) 是用 RuntimeException 类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误。运行时异常通常是由 Java 虚拟机抛出的。
  • RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类,可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明,指的就是这些问题不需要提前被预防(本质上也可以的,只不过没必要),因为只有在真正运行的时候才能发现是否发生问题,一旦在运行期间发生了问题,则一般不会修正,程序直接终端。

在这里插入图片描述

    RuntimeException、Error 以及它们的子类都称为免检异(unchecked exception )。所有其他异常(编译时异常)都称为必检异常(checked exception), 意思是指编译器会强制程序员检査并通过 try- catch 块处理它们,或者在方法头进行声明。

    在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象陚值给它,就会抛出 NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException 异常。这些都是程序中必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现。为避免过多地使用 try-catch 块,Java 语言不强制要求编写代码捕获或声明免检异常。

Java规定:

  • 必须捕获的异常,包括 Exception 及其子类,但不包括 RuntimeException 及其子类,这种类型的异常称为Checked Exception。
  • 不需要捕获的异常,包括 Error 及其子类, RuntimeException 及其子类。

三、处理编译时异常的更多知识

1. 声明异常

    在 Java 中,当前执行的语句必属于某个方法。Java 解释器调用 main 方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常( declaring exception)。只对编译时异常进行声明,、告知方法的调用者有异常。
    为了在方法中声明一个异常,就要在方法头中使用关键字 throws, 如下所示:

public void myMethodO throws IOException

    关键字 throws 表明 myMethod 方法可齙会抛出异常 IOException。如果方法可能会抛出多个异常,就可以在关键字 throws 后添加一个用逗号分隔的异常列表:

public void myMethodO throws Exceptionl, Exception2,… ,ExceptionN

    注意:如果方法没有在父类中声明异常,那么就不能在子类中对其进行继承来声明异常。

2. 抛出异常

    检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这就称为抛出一个异常(throwing an exception)。这里有一个例子,假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建 IllegalArgumentException 的一个实例并抛出它,如下所示:

IllegalArgumentException ex = new II1egalArgumentException("Wrong Argument");
throw ex;

    或者,大多数使用下面的语句:

throw new IllegalArgumentException("Wrong Argument");

    注意:IllegalArgumentException 是 Java API 中的一个异常类。通常,JavaAPI 中的每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的 String 参数的构造方法。该参数称为异常消息(exceptionmessage), 它可以用 getMessage()获取。
    提示:声明异常的关楗字是 throws, 抛出异常的关键字是 throw。

3. 捕获异常

    现在我们知道了如何声明一个异常以及如何抛出一个异常。当抛出一个异常时,可以在try-catch 块中捕获和处理它;
try-catch-finally
    try语句块中:放的是可能出现问题的代码,尽量不要把不相关的代码放入到里面,否则会出现截断的问题。

try{
    codeA
    throw ...
    codeB
}

    如果throw这个地方一旦抛出异常 codeB就不会执行了 建议把codeB放到后面。

    catch语句块中放的是出现问题并捕获后,处理问题的代码code,如果问题在try语句块中没有出现 则catch中不会运行。

catch(Exception e) {
    code
}

    finally语句块中放的是不管问题异常是否产生 都要执行的代码code。

finally{
       code//关闭资源(IO 数据库 网络),结尾处理的一些工作
}

四、在捕获异常中return的执行顺序

;