1. 怎样理解异常处理中的 catch
匹配
catch
语句可以有多条,每条对应一种异常类型- 自 Java 7 开始支持一种新的语法,多个异常之间可以用 “ | ” 操作符
2. 怎样理解重新抛出(throw
)异常
- 在
catch
块内处理完后,可以重新抛出异常,异常可以是原来的,也可以是新建的
3. 为什么要重新抛出(throw
)异常
- 因为当前代码不能够完全处理该异常,需要调用者进一步处理
4. 为什么要抛出(throw
)一个新的异常
- 当然是因为当前异常不太合适,不合适可能是信息不够,需要补充一些新信息。还可能是过于细节,不便于调用者理解和使用
- 如果调用者对细节感兴趣,还可以通过
getCause()
方法获取到原始异常
5. 怎样理解异常机制中的 finally
finally
内的代码不管有无异常发生,都会执行。具体来说- 如果没有异常发生,在
try
内的代码执行结束后执行 - 如果有异常发生且被
catch
捕获,在catch
内的代码执行结束后执行 - 如果有异常发生但没被捕获,则在异常被抛给上层之前执行
- 如果没有异常发生,在
6. 使用 finally
的场景
finally
一般用于释放资源,如数据库连接、文件流等
7. finally
语句的执行细节
- 如果在
try
或catch
语句内有return
语句,则return
语句在finally
语句执行结束后才执行,但finally
并不能改变返回值,即finally
中对返回值的修改不会被返回。(这点也是挺奇葩的)- 如果在
finally
语句中也有return
语句,那么try
或catch
内的return
会丢失,实际会返回finally
中的返回值
- 如果在
finally
中有return
不仅会覆盖try
和catch
内的返回值,还会掩盖try
和catch
内的异常,就像异常没有发生一样。也就是在异常抛出之前执行了finally
里的return
语句- 如果
finally
中抛出了异常,则原异常也会被掩盖
- 如果
- 一般而言,为避免混淆,应该避免在
finally
中使用return
语句或者抛出异常,如果调用的其他代码可能抛出异常,则应该捕获异常并进行处理,即异常嵌套
8. 怎样理解 try-with-resources
对于一些使用资源的场景,比如文件和数据库连接,典型的使用流程是首先打开资源,最后在
finally
语句中调用资源的关闭方法针对这种场景,Java 7 开始支持一种新的语法,称之为
try-with-resources
这种语法针对实现了
java.lang.AutoCloseable
接口的对象,该接口的定义为1
2
3public interface AutoCloseable {
void close() throws Exception;
}没有
try-with-resources
时,使用形式如下1
2
3
4
5
6
7
8public static void useResource() throws Exception {
AutoCloseable r = new FileInputStream("hello"); // 创建资源
try {
// 使用资源
} finally {
r.close();
}
}使用
try-with-resources
语法时,形式如下1
2
3
4
5public 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
声明中,都应该在合适的地方以适当的方式进行处理 - 观点本身不重要,重要的是一致性:一个项目中,应该对如何使用异常达成一致,并按照约定使用