异常(三):异常处理

  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` 但也没有被重新赋值)。
  1. 怎样理解 throws 关键字?
    答:

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

    • 对于未受检异常,是不要求使用 throws 进行声明的,但对于受检异常,则必须进行声明。换句话说,对于受检异常,如果没有声明,则不能抛出。
    • 对于受检异常,不可以抛出而不声明,但可以声明抛出但实际不抛出。这主要用于在父类方法中声明,父类方法内可能没有抛出,但子类重写方法后可能就抛出了。子类不能抛出父类方法中没有声明的受检异常,所以就将所有可能抛出的异常都写到父类上了。
    • 如果一个方法内调用了另一个声明抛出受检异常的方法,则必须处理这些受检异常。处理方式既可以是 catch,也可以是继续使用 throws
  3. 未受检异常和受检异常的区别?
    答:受检异常必须出现在 throws 语句中,调用者必须处理,Java 编译器会强制这一点,而未受检异常则没有这个要求

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

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