异常机制

75 阅读6分钟

异常机制

基本概念

概念: 程序执行中发生的不正常情况(语法错误和逻辑错误不是异常)

java异常机制可以使程序中异常处理代码和正常业务代码分离,提高程序健壮性

Throwable

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

Throwable 包含两个子类:Error(错误)和 Exception(异常)如下图

image-20220222105957339

Error(错误)

Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误

此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程

这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误

Exception(异常)

异常分为两大类:运行时异常,编译时异常

  • 运行时异常,编译器不要求强制处置的异常(一般是逻辑错误),运行时异常和错误为非可查异常,不需要处理
  • 编译时异常是编译器要求必须处置的异常

image-20220410133947189

编译异常

概念:编译异常又可以称为可查异常,编译异常是RuntimeException以外的异常,是编译器要求必须处置的异常

异常类型发生的场景
SQLException操作数据库时,查询表可能发生异常
lOException操作文件时,发生的异常
FileNotFoundException当操作一个不存在的文件时,发生异常
ClassNotFoundException加载类,而该类不存在时,异常
EOFException操作文件,到文件末尾,发生异常
IllegalArguementException参数异常

运行时异常

运行时异常,编译器不要求强制处置的异常(一般是逻辑错误)

空指针异常

NullPointException :当应用程序在需要使用对象的地方使用null,抛出该异常

// 实例
class NullPointException{
    public static void main(String[] args) {
        String name = null;
        System.out.println(name.length()); // NullPointerException
    }
}
数字运算异常

ArithmeticException:当出现异常运算条件时,抛出异常

// 实例
class ArithmeticException {
    public static void main(String[] args) {
        int num1 = 10,num2 = 0;
        int div = num1/num2; // ArithmeticException
    }
}
数组下标越界

ArrayIndexOutOfBoundsException:用非法索引访问数组的异常

// 实例
class ArrayIndexOutOfBoundsException {
    public static void main(String[] args) {
        int[] array = new int[3];
        System.out.println(array[3]); //ArrayIndexOutOfBoundsException
    }
}
类型转换异常

ClassCastException:当试图将对象强制类型转换为不是该实例的子类,抛出该异常

// 实例
class ClassCastException {
    public static void main(String[] args) {
         A b = new B();
         C c = (C)b; // ClassCastException
    }
}
class A{}
class B extends A{}
class C extends A{}
数字格式不正确异常

NumberFormatException:当应用程序试图将字符串转换成一种数值类型,将抛出该异常

// 实例
class NumberFormatException {
    public static void main(String[] args) {
        String name = "";
        int num = Integer.parseInt(name); // NumberFormatException
    }
}

异常处理

异常处理关键字

  • try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally – finally语句块总是会被执行。它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw – 用于抛出异常。
  • throws – 用在方法签名中,用于声明该方法可能抛出的异常。

两种处理方式

1.try - catch - finally (程序员在代码中捕获异常,自行处理)

注意:try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally

// try - catch - finally格式如下
try{
    // 代码可能有异常
    // 如果没有出现异常,则执行try块中所有的语句,反之有出现异常,则不再执行try块中剩余的语句,抛出异常
}catch(NullPointerException e){ 
    // 捕获异常
    // 1.当异常发生时,才会执行catch内的代码
    // 2.系统将异常封装成Exception对象e,传递给catch
    // 3.同一个 catch 也可以捕获多种类型异常,用 | 隔开
}catch(RuntimeException e){ 
    // 可以有多个catch语句进行捕获不同的异常,要求子类异常在父类异常之前,不然就没有子类异常存在的意义
}finally{
    // 不管是否有异常,一般都会执行
    // 总结:finally遇见如下情况不会执行
    // 1.在前面的代码中用了System.exit()退出程序。
    // 2.finally语句块中发生了异常。
    // 3.程序所在的线程死亡。
    // 4.关闭CPU。
}
​
// 可以进行try-finally配合使用,不进行其他异常捕获(包括throws),不管是否有异常都执行某些语句,然后程序退出
try{  
}finally{
}

2.throw或者throws 将异常抛出(交给调用者来处理,最顶级处理者JVM)

throws VS throw意义位置后面的语句
throws(异常的申明)异常处理的一种方式方法声明处异常类型
throw(异常的抛出)手动生成异常对象的关键字方法体中异常对象

测试代码

// throws(异常的申明)
class Father{
    // RuntimeException 是 NullPointerException 的父类
    // 运行时异常:如果没有处理,默认throws的方式处理
    public void methos() throws RuntimeException{}
}
​
class Son extends Father{
    // 子类重写的方法,所抛出的异常类型要么和父类抛出的异常一样,要么为父类抛出异常的子类型
    @Override
    public void methos() throws NullPointerException{
        super.methos();
    }
}
​
// throw (异常的抛出)
public double method(int num,int value) {
    if(value == 0) {
        throw new ArithmeticException("参数不能为0"); // 抛出一个运行时异常
    }
    return num / value;
}

throw或者throws 抛出形式:

image-20220222155846503

自定义异常

继承Throwable的子类或者间接子类

运行时异常通常继承RuntimeException;编译时异常通常继承Exception

// 例子
class test{
    public static void main(String[] args) {
        int age = 208;
        /*
         * Exception in thread "main" com.Al_tair.exception_.AgeJudge: 不符合年龄
         *  at com.Al_tair.exception_.test.main(Exception01.java:71)
         */
        if(!(age >= 0 && age <= 140)){
            throw new AgeJudge("不符合年龄");
        }
    }
}
class AgeJudge extends RuntimeException{
    public AgeJudge(String message) {
        super(message);
    }
}

JVM处理异常的机制

异常表

异常发生时的机制如下 取自java全栈知识体系

  1. JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理
  2. 如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理
  3. 如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目
  4. 如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作
  5. .如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止
  6. 如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行
// 测试代码 test.java
public class test{
    public static int[] list  = new int[2];
    public static void main(String[]args){
        try {
            int a = list[2];
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
# 反编译 test.class文件
public class test {
  public static int[] list;

  public test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field list:[I
       3: iconst_2
       4: iaload
       5: istore_1
       6: goto          14
       9: astore_1
      10: aload_1
      11: invokevirtual #15                 // Method java/lang/Exception.printStackTrace:()V
      14: return
    Exception table:   异常表
    # from 可能发生异常的起始点
    # to 可能发生异常的结束点
    # target 上述from和to之前发生异常后的异常处理者的位置
    # type 异常处理者处理的异常的类信息
       from    to  target type
           0     6     9   Class java/lang/Exception

  static {};
    Code:
       0: iconst_2
       1: newarray       int
       3: putstatic     #7                  // Field list:[I
       6: return
}