0%

Java 异常(三):异常处理

1. 怎样理解异常处理中的 catch 匹配

  • catch 语句可以有多条,每条对应一种异常类型
  • Java 7 开始支持一种新的语法,多个异常之间可以用 “ | ” 操作符

2. 怎样理解重新抛出(throw)异常

  • catch 块内处理完后,可以重新抛出异常,异常可以是原来的,也可以是新建的

3. 为什么要重新抛出(throw)异常

  • 因为当前代码不能够完全处理该异常,需要调用者进一步处理

4. 为什么要抛出(throw)一个新的异常

  • 当然是因为当前异常不太合适,不合适可能是信息不够,需要补充一些新信息。还可能是过于细节,不便于调用者理解和使用
  • 如果调用者对细节感兴趣,还可以通过 getCause() 方法获取到原始异常

5. 怎样理解异常机制中的 finally

  • finally 内的代码不管有无异常发生,都会执行。具体来说
    • 如果没有异常发生,在 try 内的代码执行结束后执行
    • 如果有异常发生且被 catch 捕获,在 catch 内的代码执行结束后执行
    • 如果有异常发生但没被捕获,则在异常被抛给上层之前执行

6. 使用 finally 的场景

  • finally 一般用于释放资源,如数据库连接、文件流等

7. finally 语句的执行细节

  • 如果在 trycatch 语句内有 return 语句,则 return 语句在 finally 语句执行结束后才执行,但 finally 并不能改变返回值,即 finally 中对返回值的修改不会被返回。(这点也是挺奇葩的)
    • 如果在 finally 语句中也有 return 语句,那么 trycatch 内的 return 会丢失,实际会返回 finally 中的返回值
  • finally 中有 return 不仅会覆盖 trycatch 内的返回值,还会掩盖 trycatch 内的异常,就像异常没有发生一样。也就是在异常抛出之前执行了 finally 里的 return 语句
    • 如果 finally 中抛出了异常,则原异常也会被掩盖
  • 一般而言,为避免混淆,应该避免在 finally 中使用 return 语句或者抛出异常,如果调用的其他代码可能抛出异常,则应该捕获异常并进行处理,即异常嵌套

8. 怎样理解 try-with-resources

  • 对于一些使用资源的场景,比如文件和数据库连接,典型的使用流程是首先打开资源,最后在 finally 语句中调用资源的关闭方法

  • 针对这种场景,Java 7 开始支持一种新的语法,称之为 try-with-resources

    • 这种语法针对实现了 java.lang.AutoCloseable 接口的对象,该接口的定义为

      1
      2
      3
      public interface AutoCloseable {
      void close() throws Exception;
      }
    • 没有 try-with-resources 时,使用形式如下

      1
      2
      3
      4
      5
      6
      7
      8
      public static void useResource() throws Exception {
      AutoCloseable r = new FileInputStream("hello"); // 创建资源
      try {
      // 使用资源
      } finally {
      r.close();
      }
      }
    • 使用 try-with-resources 语法时,形式如下

      1
      2
      3
      4
      5
      public static void useResource() throws Exception {
      try(AutoCloseable r = new FileInputStream("hello")) { // 创建资源
      // 使用资源
      }
      }
  • 资源 r 的声明和初始化放在 try 语句内,不用再调用 finally,在语句执行完 try 语句后,会自动调用资源的 close() 方法,对程序员更加友好(懒是第一生产力)

  • 资源可以定义多个,以分号分隔。在 Java 9 之前,资源必须声明和初始化在 try 语句块内,Java 9 去除了这个限制,资源可以在 try 语句外被声明和初始化,但必须是 final 的或者是事实上 final 的(即虽然没有声明为 final 但也没有被重新赋值)

9. 怎样理解 throws 关键字

  • 用于声明一个方法可能抛出的异常throws 跟在方法的括号后面,可以声明多个异常,以逗号分隔
  • 这个声明的含义是:这个方法内可能抛出这些异常,且没有对这些异常进行处理,至少没有处理完,调用者必须进行处理
  • 这个声明没有说明具体什么情况会抛出异常,作为一个良好的实践,应该将这些信息用注释的方式进行说明,这样调用者才能更好地处理异常

10. throws 关键字的细节

  • 对于未受检异常,是不要求使用 throws 进行声明的,但对于受检异常,则必须进行声明。换句话说,对于受检异常,如果没有声明,则不能抛出
  • 对于受检异常,不可以只抛出而不声明,但可以声明抛出但实际不抛出。这主要用于在父类方法中声明,父类方法内可能没有抛出,但子类重写方法后可能就抛出了。子类不能抛出父类方法中没有声明的受检异常,所以就将所有可能抛出的异常都写到父类上了
  • 如果一个方法内调用了另一个声明抛出受检异常的方法,则必须处理这些受检异常。处理方式既可以是 catch,也可以是继续使用 throws

11. 未受检异常和受检异常的区别

  • 受检异常必须出现在 throws 语句中,调用者必须处理,Java 编译器会强制这一点
  • 未受检异常则没有这个要求

12. 为什么要有这个区分?我们自定义异常的时候使用使用受检还是未受检异常

  • 对于这个问题,业界有各种各种的观点和争论,没有特别一致的结论
  • 目前一种更被认同的观点是:Java 中对受检异常和未受检异常的区分是没有太大意义的,可以统一使用未受检异常来代替
  • 无论是受检异常还是未受检异常,无论是否出现在 throws 声明中,都应该在合适的地方以适当的方式进行处理
  • 观点本身不重要,重要的是一致性:一个项目中,应该对如何使用异常达成一致,并按照约定使用
-------------------- 本文结束感谢您的阅读 --------------------