【Java异常处理】6-2 抛出和捕获异常

§6-2 抛出和捕获异常

本节内容将介绍如何处理异常。

6-2.1 处理异常的方式

在 Java 中,处理异常的方式有以下三种:

  • JVM 默认处理方式:将异常名称、异常原因、一场出现的代码行等信息打印到控制台上,并结束程序;
  • 产生异常的方法内部自行处理;
  • 交由调用者处理,向调用者抛出异常;

6-2.2 使用 try, catch, finally 环绕自行处理异常

若使用 JVM 的默认异常处理方式,程序会在异常代码处终止运行。在可能抛出异常的语句使用 try-catch 语句环绕,可让程序继续运行。若调用一个可能会抛出异常的方法,在调用语句使用该语句块环绕也可以防止程序终止运行。

语法

try {
    //保护代码块,监控区域
} catch (ExceptionType e1) {
    //捕获区域
} catch (ExceptionType e2) {
    //捕获区域,可进行多重捕获
} finally {
    //无论是否发生异常,finally代码块中的代码都会被执行,可选
    //可以运行善后性质的语句,例如用于关闭流
}

注意

  • try 后面不能既没有 catch 也没有 finally
  • catch 不能独立于 try 而存在;
  • trycatchfinally 间不能夹有其他代码。

示例:在主方法中:

public static void main(String[] args) {
    int a = 1;
    int b = 0;
    
    //使用try catch环绕,在IDEA中可选中代码,按下组合键Ctrl + Alt + T选择,自动生成代码块
    try {
        //监控区域
        System.out.println(a/b);
    } catch (ArithmeticException e) {
        //捕获区域
        System.out.println("被除数不能为0");
        e.printStackTrace();	//打印异常信息
        System.exit(1);			//终止程序,通常以非零数表示非正常终止
    } finally {
        System.out.println("Finally");
    }
}

其中 catch 后的参数为所捕获的异常类型,若明确已知抛出的异常类型,可以填上具体的异常类型,否则,可填 Exception(捕获异常)、Error(捕获错误)、Throwable(前面二者的超类),随后跟着一个变量,在这一例子中,变量为 e

运行,得到

被除数不能为0
java.lang.ArithmeticException: / by zero
	at com.oop.exception.Test.main(Test.java:11)
	
进程已结束,退出代码1

至此,程序非正常终止。

多重捕获

但是,程序运行时产生的往往可能不止一种错误,catch 语句允许类似于 if else 结构一样,进行多重捕获。其捕获的顺序是自上而下,直至遇到第一个匹配的异常或者通过所有的捕获块。因此,建议将高级异常写在后面,防止被提前捕获,并捕获可能遗漏的异常

public static void main(String[] args) {
    int[] arr = {};	//长度为零的数组
    try {
        System.out.println(getMax(arr));
    } catch (NullPointerException e) {
        System.out.println("空指针异常");
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("数组长度为零。")
    }
}

public static int getMax(int[] arr) {
    //throw 关键字在下文介绍,此处可先简单理解为产生异常并结束运行该方法
    if (arr == null) {
        //若数组为空
        throw new NullPointerException();
    }
    if (arr.length == 0) {
        //数组长度为零
		throw new ArrayIndexOutOfBoundsException(0);
    }
    
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max)
            max = arr[i];
    }
    
    return max;
}

运行得到

被除数不能为0

进程已结束,退出代码0

另外,也可以使用一个 catch 块捕获多个异常,只需要在不同的异常间使用 | 分隔开即可,只要 try 语句块中生成的异常对象能够被其中一个异常捕获,则该 catch 块就会执行。例如:

int[] arr = {1,2,3,4,5};
try {
    System.out.println(arr[5]);
    System.out.println(2/0);	//由于上一句已经抛出异常,这一句(及其后的)会被短路
} catch (ArrayIndexOutOfBoundsException e | ArithmeticException e) {
    System.out.println("抛出异常");
}

运行过程

  • try 代码块中的代码没有产生问题,则其中代码全部执行,catch 块不会执行(有且仅有抛出异常且被捕获才会执行);
  • try 代码块中的代码出现了异常,会创建对应异常的对象,try 出现异常语句之后的所有语句将会逃过;
  • 如果 try 可能会出现多个问题,则应当用多个 catch 多重捕获;
  • 随后,该异常对象会以顺序结构逐个与 catch 中的异常做对比,看看其括号中的变量能否接收这个异常对象;
  • 若找不到可以接收的异常,则会将异常交由 JVM 默认处理;
  • 若能接收,则该异常被捕获,执行该捕获块中的代码,其后的捕获块将会被忽略;
  • 捕获的异常若具有继承关系,父类异常一定要位于后面(次序靠后);
  • 当所有代码执行完毕,执行 finally 块中代码(如果有的话),随后执行 try-catch 语句块后的代码;

6-2.3 异常中的常见方法

整个异常处理框架最顶级的超类是 Throwable,该类提供了一些异常的处理方法。

常用方法

方法 描述
String getMessage() 返回此 Throwable 的细节信息
String toString() 返回此 Throwable 的简要描述
void printStackTrace() 将此 Throwable 及其回溯打印到标准错误流

注意

  • "回溯" 英文原文为 backtrace,实际上呈现的是方法调用关系的一个功能;
  • 一般而言,不建议处理异常时,仅仅简单地将错误信息打印到控制台上;
  • getMessage() 返回的是异常的细节信息,例如索引越界 Index x out of bounds for length l
  • toString() 返回的是异常名称 + 细节信息;
  • printStackTrace() 用红色的字体在控制台中输出错误信息,该方法更常用,含有更多信息。

6-2.4 throwthrows 抛出异常

try-catch 一般用于方法调用处,防止程序终止运行。throwthrows 关键字都是用于方法,效果都是向方法调用者抛出异常,但二者具有一些区别。

**throw **:throw 关键字写在方法内,用于结束方法,并手动抛出异常对象,交给调用者。抛出对象后,方法下面的代码将不再执行。

public void test(int a, int b) {
    //相除
    if (b == 0) {
        throw new ArithmeticException();	//在方法中主动抛出异常
    }
}

在主方法中调用该方法,得到

Exception in thread "main" java.lang.ArithmeticException
	at com.oop.exception.Test.test(Test.java:29)
	at com.oop.exception.Test.main(Test.java:23)

可以在方法内在 throw 字段使用 try-catch 环绕处理异常。

throws:写在方法签名处,用于声明异常,告诉调用者,调用本方法可能会抛出什么异常。

public void test(int a, int b) throws ArimeticException {
    System.out.println(a/b);
}

此时,在调用该方法时,需要用 try-catch 环绕:

try {
    test(1,0);
} catch (Exception e) {
    System.out.println("被除数不能为0");
}

运行,得到

被除数不能为0

进程已结束,退出代码0

一个方法也可以抛出多个异常,只需要在不同的异常之间用 , 分隔即可。

这种方法可以向调用者告知底层执行情况,让调用者处理异常。

注意throws 位于方法的签名,若为编译时异常,则必须要写,运行时异常可以不写。

回顾:在面向对象的章节中,曾有讲过实现 Cloneable 接口重写 clone() 方法的复制对象方法。这时,重写方法必须要向外抛出 CloneNotSupportedException 异常。

6-2.5 assert 断言关键字

断言是一种在开发中常用的技术手段,用于判断程序执行所需要的条件是否满足。

用法

assert expression : "Error message(Optional, but recommended)";

执行时,会首先计算布尔表达式 expression 的值,若为 true,程序继续正常执行。若为 false,则抛出错误 AssertionEror,并附带所给的 Error message,程序中止执行。

所抛出的 AssertionError 是错误,该错误不能够被 try-catch 语句块捕获。

assert 关键字常用于开发和测试环境中的预警处理,例如用于检查条件是否满足、捕获非法情况(并非错误情况)、确认方法参数。这样做可在开发和测试阶段定位和检查程序潜在的错误,提高程序健壮性、代码可读性和可维护性。但在实际生产工作环境中,应当极力避免断言失败而导致程序的异常退出或中断。因此,JVM 在默认情况下禁用断言

若要启用断言,可在启动程序时在命令行中添加参数 -enableassertions 或其简称 -ea 启用断言。若要禁用断言,可添加参数 -disableassertions 或其简称 -da 以禁用断言。

使用建议

  1. 仅在开发和测试环境中启用断言以检查潜在错误,使用断言时应当提供清晰、易读的错误信息;
  2. 不要滥用断言,断言会在条件不满足时强制抛出错误并终止程序;
  3. 每一句断言只检查一个条件,这样更直观地判断失败条件;
  4. 不要在断言中使用改变环境的语句,一旦条件不满足,改变环境的语句可能不会执行;
  5. 断言应当与后面的语句空出一行,形成逻辑和视觉上的一致感;

工作流程

  1. 编译器阶段:编译时,编译器会通过检查 assert 语句的语法和语义生成相应的字节码指令。若表达式为 true,则生成一条空指令 nop;否则抛出错误 AssertionError
  2. 运行时阶段:当程序执行到 assert 语句时,若条件表达式为 true,则跳过该语句,程序继续正常执行;否则,生成一条带有给定错误信息的 AssertionError 并抛出。该错误是一个严重错误,且无法用 try-catch 捕获,程序会终止执行。

6-2.6 参考链接

Java 断言 assert 你真的会用嘛? - 掘金 (juejin.cn)

断言(assert)的用法 - thisway_diy - 博客园 (cnblogs.com)

posted @ 2023-07-14 21:58  Zebt  阅读(287)  评论(0)    收藏  举报