一、概述

万物皆对象,异常在Java中也是一种对象。在Java中,把常见的异常情况,都抽象成了对应的异常类型,那么每种异常类型都代表了一种特定的异常情况。
当程序出现异常情况时,会创建并抛出和该异常情况对应的异常类的对象,这个异常对象中保存了一些信息,用来表示当前程序到底发生了什么异常情况。
通过异常信息,我们可以定位异常发生的位置,以及异常发生的原因。
异常分为两种,Java提供的异常类型称为原生异常,此外也可以自定义异常。
异常体系中的根类是:java.lang.Throwable,该类下面有两个子类型:java.lang.Error和java.lang.Exception。其中Error是严重的错误,程序自身不能够解决;而Exception是异常,能够通过特定的方式进行处理和纠正,平常表示的异常都是这种异常。 异常 - 图1Exception中并没有定义方法,它的方法都是从Throwable中继承过来的,其中常用的有:

  • printStackTrace():打印输出当前发送异常的详细信息(重要)。
  • getMessage():返回异常对象抛出是携带的信息,一般是异常的发生原因(重要)。
  • printStackTrace(PrintWriter s):可以指定字符输出流,对异常信息进行输出。
  • printStackTrace(PrintStream s):可以指定字节输出流,对异常信息进行输出。

异常主要分为两种:

  • 运行时异常:编译器不会报错,之后在运行时抛出。
  • 编译时异常:又称检查异常,编译器会报错。

出现异常后的处理的方法有两种:

  • 将异常抛出:基于throws关键字对异常进行抛出,并可以在方法中对异常使用throw进行手动抛出。
  • 捕获异常并处理:基于try-catch-finally结构对异常进行捕获和处理。

方法在异常抛出后便不会再执行。

二、抛出

JVM会在出现了预设的异常情况之时,自动创建异常对象,抛出、捕获并处理(打印)。
也可以对可能出现的异常在方法中使用throw手动抛出。

  • 若该异常为编译时异常,则需要在方法体之前使用throws关键字将异常进行声明。
  • 若该异常为运行时异常,则不需要使用throws关键字进行声明。

当方法将异常抛出后,若异常一直没有被调用方法的代码处理而继续抛出(即没有方法对此进行处理),JVM会自动捕获并通过printStackTrace()方法将异常打印出来。

  1. public void test(String name) { // 运行时异常不用throws
  2. if(!"tom".equals(name)) {
  3. throw new RuntimeException("用户名和预期不符!");
  4. }
  5. }
  6. public void test(String name) throws Exception {
  7. if(!"tom".equals(name)) {
  8. throw new Exception("用户名和预期不符!");
  9. }
  10. }

三、捕获和处理

若不想让方法对异常进行抛出,让JVM捕获和处理,可以使用try-catch代码块进行捕获和处理。在try块中,会对其中的代码进行捕获;在catch块中,对特定的异常进行处理。

  • catch可以使用多次,对特定的异常进行处理。需先捕获范围小的异常,再捕获范围大的异常。
  • 也可以使用|对异常进行捕获。
  • 也可以不使用catch捕获异常,此时需要在方法体之前声明编译时异常。
    1. try {}
    2. catch(RuntimeException e) {} // 先捕获范围小的异常
    3. catch(Exception e) {} // 再捕获范围大的异常
    4. /***********分割线***********/
    5. try {}
    6. catch(ClassNotFoundException | NoSuchMethodException e) {}
    在捕获并处理异常后,方法会终止。若try块中还有后续代码,则不会被执行。故引入了finally关键字,无论是否发生异常,finally块内的代码都会被执行。同时,finally块的代码执行是在return之后的,即使发生了return,finally代码依然会执行。若finally代码中存在return,会覆盖先前的return。
    1. try {
    2. return 10/0;
    3. return 1;
    4. } catch(Exception e) {
    5. return 2;
    6. } finally {
    7. return 3; // 最后返回3
    8. }
    9. /***********分割线***********/
    10. int i = 0;
    11. try {
    12. return 10/0;
    13. return i++;
    14. } catch(Exception e) {
    15. return i++; // 最后返回0
    16. } finally {
    17. i++;
    18. }
    当然,try-catch-finally是配合执行的,当try代码没有被执行时,finally代码也不会被执行。在try块或catch块时被打断(interrupted)或者被终止(killed),或执行了 System.exit(0)语句终止了JVM的运行,finally代码也都不会被执行。
    针对流等资源类型对象的异常处理(try-with-resource资源自动释放机制),参见节点流

    四、自定义异常

    JDK中存在的异常都是与程序编译和运行的关键错误有关的。若程序的使用者使用了程序的编写者不允许的非法指令,如年龄设置为负数、成绩大于满分,用户登录时密码不正确、用户访问某些接口时候的权限不足等,这时则需要使用自定义异常。可以定义一个类继承RuntimeException(运行时异常)或Exception(编译异常),并使用父类的构造方法,即可自定义一个异常。一般继承RuntimeException。
    1. public class ModifyUserInfoException extends RuntimeException { // 自定义一个用户信息修改异常,继承运行时异常类
    2. public ModifyUserInfoExceptin() {}
    3. public ModifyUserInfoExceptin(String message) {
    4. super(message); // 调用父类的构造方法
    5. }
    6. }
    7. // 抛异常时
    8. throw new ModifyUserInfoException("权限错误");
    抛出信息如果每次都不一样,日志就会混乱。可以使用枚举对异常的抛出信息进行统一约定:
    1. public enum ExceptionType {
    2. ACCESS_ERROR("权限错误"); // 定义一个权限错误异常
    3. private String message;
    4. private ExceptionType(String message) { // 构造器
    5. this.message = message;
    6. }
    7. public String getMsg() {
    8. return message;
    9. }
    10. }
    11. // 抛异常时
    12. throw new ModifyUserInfoException(ExceptionType.ACCESS_ERROR.getMsg());

    五、异常预处理

    编译异常在使用IDE编写程序的时候即出现,故编译异常是能够在编译前处理和避免的。
    而为了避免运行时异常,则需要注意以下几点:
    (一)对变量可能的值进行预见
    对于基本数据类型,需要对数据的正负、是否为0进行判断,避免出现不应为负值的值为负、除数为0等情况。
    对于引用类型,需要对引用类型是否为null进行判空处理,避免对象在使用方法时,对象为空的情况(空指针异常)。同时,字符串在进行比较时,一般使用”固定”.equals(变量),而不是变量.equals(“固定”),防止变量为null的情况。
    (二)对数组的范围进行预见
    在存入数据时,需要对数组的元素数量的当前长度进行比较,确定是否能够存入。
    (三)对数据强转的类型进行预见
    在进行类型强制转换时,需要对对象是否属于某个类进行判断(如instanceof),确定是否能够强转。

    六、断言

    断言(assert)是JDK1.4中增加的关键字。可以在程序中,使用断言确认一些关键性条件必须成立(true),否则会抛出AssertionError类型的错误。
    默认情况下,JVM没有开启断言功能,需要通过给JVM传参(-enableassertions 或 -ea)来开启此项功能。
    1. assert a != 0 : "参数a的值不能为0";
    2. // a为0时出现异常:Exception in thread "main" java.lang.AssertionError: 参数a的值不能为0