try-catch-finally
一.执行顺序
try-catch-finally 包含的代码块,当 try 里面的代码出现异常的时候,会进入 catch 中,finally 代码块则在最后被执行,即 无论是否出现异常,finally 里面的代码块都会被执行,但是如果 try 和 finally ,catch 和 finally 或 try , catch, finally 中都有 return 语句的话,它的返回值该是多少呢?接下来看下每种情况的输出如何:
用例1:只有 try 里面有 return 语句
public static int test1(){
int i = 1;
try {
return i+5;
}catch (Exception e){
System.out.println("异常");
}finally {
}
return i;
}
输出:6
结论:只有 try 里面有 return 语句,则直接返回。
用例2:只有 try 里面有 return 语句,但是在 finally 里面对返回的值进行了修改,则应该返回什么呢?
public static int test(){
int i = 1;
try {
i++;
return i;
}catch (Exception e){
System.out.println("异常");
}finally {
i++;
}
return i;
}
输出:2
上面的代码,finally 一定会被执行,当执行完 finally 之后,i 的值变为了 3, 但是,返回的却还是 2,debug 跟踪如下:
当执行完 try 语句后,i 为 2:

接下来会执行 finally,之后, i 变为 3:


最终返回的结果还是 2,也就是说,当执行完 finally 中的代码后,还会进入到 try 中进行返回,且返回的是 finally 修改之前的值,
结论:只有 try 里面有 return 语句的时候,finally 里面对 return 值的修改将不会影响返回值
用例3:try, finally 中都有 return 语句
public static int test(){
int i = 1;
try {
i++;
return i;
}catch (Exception e){
System.out.println("异常");
}finally {
i++;
return i;
}
}
输出:3
如上述代码所示,当 try 和 finally 中都有 return 语句的时候,finally 中的 return 会覆盖 try 中的 return
结论:当 try 和 finally 中都有 return 语句的时候,finally 中的 return 会覆盖 try 中的 return,方法的返回值以 finally 中返回的为准。
用例4:当只有 try 和 finally 有 return 语句,且 try 中抛异常
public static int test(){
int i = 1;
try {
i = i / 0;
return i;
}catch (Exception e){
}finally {
i++;
return i;
}
}
输出:2
结论:当 try 中抛异常后,会进入到 catch 中,最后会进入 finally 中进行返回。
用例5:当只有 try 和 finally 有 return 语句,且 try 中抛异常,在 catch 中对值进行修改:
public static int test(){
int i = 1;
try {
i = i / 0;
return i;
}catch (Exception e){
i++;
}finally {
i++;
return i;
}
}
输出:3
结论:方法以 finally 中的返回值为准。
用例6:当只有 try 和 catch 中有 return 语句时候:
try 中无异常:
public static int test(){
int i = 1;
try {
return i;
}catch (Exception e){
i++;
return i;
}finally {
i++;
}
输出:1
try 中有异常:
public static int test(){
int i = 1;
try {
i = i / 0;
return i;
}catch (Exception e){
i++;
return i;
}finally {
i++;
}
输出:2
结论:当 只有 try 和 catch 中有 return 语句的时候,如果 try 中没有抛异常,则返回 try 中的值,如果 try 中抛异常,则返回 catch 中的值,同用例2一样,finally 中对返回的值进行修改,则还是返回修改之前的值,修改无效。
用例7:try,catch 和 finally 中都有 return 语句:
public static int test(){
int i = 1;
try {
i = i / 0;
return i;
}catch (Exception e){
i++;
return i;
}finally {
i++;
return i;
}
}
输出:3
结论:以 finally 中的返回值为准。
总结
1.当 try 中抛异常的时候,会进入 catch 中
2.finally 块中无论是否抛异常都会被执行
3.当只有 try 中或者 catch 中有 return 语句的时候,finally 中对返回值的修改将无效,还是返回修改之前的值。
4.只要 finally 中有 return 语句,则方法的返回值将以 finally 中返回的为准。即 finally 中的 return 将会覆盖 try 中的 return 或 catch 中的 return。
JDK 1.7 后的变化
在 try-catch-finally 中,一般需要在 finally 中进行一些资源的清理工作。在 JDK 1.6 之前的,是必须在 finally 中进行清理的,但是在 JDK 1.7 之后,提供了 try-catch-resource 语法。
JDK 1.7 以前的写法如下:
public static void test() throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
fis.read();
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
if (fis != null){
fis.close();
}
}
}
如上代码所示,如果在 try 中调用 read() 抛出异常,在 finally 中 调用 close() 也出现异常,则返回会向外抛出哪个异常呢?下面来试验一下:
首先声明一个方法抛出异常:
class MyFileInputStream implements AutoCloseable{
public void read() throws Exception{
throw new Exception("read() 出现异常");
}
@Override
public void close() throws Exception {
throw new Exception("close() 出现异常");
}
}
测试:
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
try{
test();
}catch (Exception e){
e.printStackTrace();
}
}
public static void test() throws Exception {
MyFileInputStream fis = null;
try {
fis = new MyFileInputStream();
fis.read();
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
if (fis != null){
fis.close();
}
}
}
}
test() 方法会抛出,异常打印如下:
java.lang.Exception: close() 出现异常
at myspringboot.myspringboot.other.MyFileInputStream.close(Main.java:40)
at myspringboot.myspringboot.other.Main.test(Main.java:27)
at myspringboot.myspringboot.other.Main.main(Main.java:12)
可以看到,方法最终抛出的异常将以 finally 中抛出的异常为准,在 try 中抛出的异常被 “吃掉” 了。
在 JDK 1.7 之后,JDK 提供可 try-catch-resource 的语法糖,写法如下:
public static void test(){
try (MyFileInputStream mfis = new MyFileInputStream()) {
mfis.read();
}catch (Exception e){
e.printStackTrace();
}
}
将需要关闭的资源写在 try 后面的括号里面,这样 JDK 就会自动的调用资源的close() 方法关闭资源,但是,要关闭的资源需要实现 AutoCloseable或者Closeable接口。
采用这种写法,出现异常的时候,方法会抛出哪个异常呢?try 里面的 还是 close() 里面的呢?接下来看下:
执行上述方法,异常打印如下:
java.lang.Exception: read() 出现异常
at myspringboot.myspringboot.other.MyFileInputStream.read(Main.java:45)
at myspringboot.myspringboot.other.Main.test(Main.java:21)
at myspringboot.myspringboot.other.Main.main(Main.java:12)
Suppressed: java.lang.Exception: close() 出现异常
at myspringboot.myspringboot.other.MyFileInputStream.close(Main.java:49)
at myspringboot.myspringboot.other.Main.test(Main.java:22)
... 1 more
可以看到,方法抛出的异常是在 try 中抛出 的那个异常,刚好和之前的相反;但是 JDK 自动调用 close() 方法出现的异常也会跟在后面,也就是说,try中的异常和 close() 中的异常都会往外抛。
如果我们想获取 close() 抛出的异常,即第二个异常,怎么获取呢?可以通过 getSuppressed() 方法来获取:
public static void test(){
try (MyFileInputStream mfis = new MyFileInputStream()) {
mfis.read();
}catch (Exception e){
System.out.println(e.getMessage());
System.out.println((e.getSuppressed())[0].getMessage());
e.printStackTrace();
}
}
异常打印如下:
read() 出现异常
close() 出现异常
java.lang.Exception: read() 出现异常
at myspringboot.myspringboot.other.MyFileInputStream.read(Main.java:47)
at myspringboot.myspringboot.other.Main.test(Main.java:21)
at myspringboot.myspringboot.other.Main.main(Main.java:12)
Suppressed: java.lang.Exception: close() 出现异常
at myspringboot.myspringboot.other.MyFileInputStream.close(Main.java:51)
at myspringboot.myspringboot.other.Main.test(Main.java:22)
... 1 more
反编译该 class 文件如下:
public static void test() {
try {
MyFileInputStream mfis = new MyFileInputStream();
Throwable var1 = null;
try {
mfis.read();
} catch (Throwable var11) {
var1 = var11;
throw var11;
} finally {
if (mfis != null) {
if (var1 != null) {
try {
mfis.close();
} catch (Throwable var10) {
var1.addSuppressed(var10);
}
} else {
mfis.close();
}
}
}
} catch (Exception var13) {
var13.printStackTrace();
}
}
从上述的反编译出来的代码可以看到,try-catch-resource 中 资源的 close() 方法最终也是会在 finally 中进行关闭。只不过,JDK 包装了一层,使用起来更加简洁。此外,还可以看到,当出现异常的时候,只有 try 向外 throw 异常,而 close() 出现的异常,只会添加到 try 抛出的异常后面(addSuppressed)。