[JAVA 基础] try-catch-finally 带 return 的执行顺序

先说结论:

1、finally 中的代码总会被执行。当 try、catch 中有 return 时,也会执行 finally。
2、finally 中没有 return 时,当 try 或 catch 中有return 的时候,会先保存返回值,再执行finally代码,最后再将返回值返回。所以要注意返回值的类型,如果返回值是引用类型,如返回list,map,则可能存在在finally代码修改返回值变量,最终返回内容受到 finally 中代码的影响。如果返回值是基本类型,则不会受finally的代码影响。
3、finally 中有 return 时,会直接在 finally 中退出,导致 try、catch 中的 return 失效。

顺便提一下:finally 中的代码一定会执行吗?

如果是之前我会毫不犹豫的说“是的”,但在遭受了社会的毒打之后,我可能会这样回答:正常情况下 finally 中的代码一定会执行的,但如果遇到特殊情况 finally 中的代码就不一定会执行了,比如下面这些情况:

  • 在 try-catch 语句中执行了 System.exit;
  • 在 try-catch 语句中出现了死循环;
  • 在 finally 执行之前掉电或者 JVM 崩溃了。

异常处理中,try、catch、finally 的执行顺序,大家都知道是按顺序执行的。即,如果 try 中没有异常,理想顺序为 try→finally,如果 try 中有异常,则顺序为 try→catch→finally。

图片.png

而且,try-catch的异常处理机制是支持嵌套的。如果异常没有在当前被捕获上,则会沿着调用栈继续往外抛出,直到最终被捕获,或程序因该异常无捕获而终止执行。

图片.png

但是当 try、catch、finally 中加入 return 之后,就会有几种不同的情况出现,下面通过简单的测试demo来了解一下。

case1、try 中 return 的基本类型

/**
* 基本类型,try中return
*/
public static int tryReturn_ValueType() {
    int i = 1;
    try {
        i = 3;
        return i;
    } finally {
        i = 5;
        System.out.println("finally:" + i);
    }
    // return i; // Unreachable statement 编辑器会直接提示不可达代码
}

@Test
public void test_tryReturn_ValueType() {
    System.out.println("get-tryReturn_ValueType-value:" + tryReturn_ValueType());
}

如果不看测试结果的话,你觉得输出结果是什么呢?

请暂停向下滑动~~思考一下上面的问题。

如果你发现自己没有答对,那就可以继续往下看~

case1 结果

finally:5
get-tryReturn_ValueType-value:3

case1 原因分析

因为当 try 中带有 return 时,会先执行 return 前的代码,然后暂时保存需要 return 的信息,再执行 finally 中的代码,最后再通过 return 返回之前保存的信息。所以,这里方法返回的值是 try 中计算后的 3,而非 finally 中计算后的 5。

仅仅是这么简单理解就行了么?

同样的栗子,再看另外一个case。

case2、try 中 return 的引用类型

/**
    * 引用类型,try中return
    */
public List<Integer> tryReturn_ReferenceType() {
    List<Integer> list = new ArrayList<>();
    try {
        list.add(1);
        System.out.println("try:" + list);
        return list;
    } catch (Exception e) {
        list.add(2);
        System.out.println("catch:" + list);
    } finally {
        list.add(3);
        System.out.println("finally:" + list);
    }
    return list;
}

@Test
public void test_tryReturn_ReferenceType() {
    System.out.println("get-tryReturn_ReferenceType-value:" + tryReturn_ReferenceType());
}

case2 结果

try:[1]
finally:[1, 3]
get-tryReturn_ReferenceType-value:[1, 3]

case2 原因分析

看完这个例子,可能会发现问题,刚提到 return 时会临时保存需要返回的信息,不受 finally 中的影响,为什么这里会有变化?其实问题出在 参数类型的传递值的区别上,上一个例子用的是基本类型,这里用的引用类型。list 存的不是变量本身,而是变量的地址,所以当 finally 通过地址改变了变量,还是会影响方法返回值的。

值类型:传递的是对象的副本,既可以理解为copy了一份,不影响原对象。
引用类型:传递的是对象的存储地址,后续的改变,都会改变地址指向的内容。

case3、catch 中带 return 的基本类型

/**
* 基本类型,catch中return
*/
public int catchWithReturn_ValueType() {
int i = 1;
try {
    i++;
    System.out.println("try:" + i);
    int x = i / 0;
} catch (Exception e) {
    i++;
    System.out.println("catch:" + i);
    return i;
} finally {
    i++;
    System.out.println("finally:" + i);
}
return i;
}

@Test
public void test_catchWithReturn_ValueType() {
System.out.println("get-catchWithReturn_ValueType-value:" + catchWithReturn_ValueType());
}

case3 结果

try:2
catch:3
finally:4
get-catchWithReturn_ValueType-value:3

case3 原因分析

catch 中 return 与 try 中一样,会先执行 return 前的代码,然后暂时保存需要 return 的信息,再执行 finally 中的代码,最后再通过 return 返回之前保存的信息。所以,这里方法返回的值是 try、catch 中累积计算后的 3,而非 finally 中计算后的 4。

case4 catch 中带 return 的引用类型

/**
* 引用类型,catch中return
*/

public List<Integer> catchWithReturn_referenceType() {
List<Integer> list = new ArrayList<>();
try {
    list.add(1);
    System.out.println("try:" + list);
    int x = 1 / 0;
} catch (Exception e) {
    list.add(2);
    System.out.println("catch:" + list);
    return list;
} finally {
    list.add(3);
    System.out.println("finally:" + list);
}
return list;
}

@Test
public void test_catchWithReturn_referenceType() {
System.out.println("get-catchWithReturn_referenceType-value:" + catchWithReturn_referenceType());
}

case4 结果

try:[1]
catch:[1, 2]
finally:[1, 2, 3]
get-catchWithReturn_referenceType-value:[1, 2, 3]

case4 原因分析

原因可以参考 case2 中的说明。

case5、finally 中带有 return

/**
    * 基本类型,try和finally中都return
    */
public int tryandfinally_with_return_valueType() {
    int i = 1;
    try {
        i++;
        System.out.println("try:" + i);
        return i;
    } finally {
        i++;
        System.out.println("finally:" + i);
        return i; // 一般IDE会提示不建议在finally中return
    }
}

@Test
public void test_tryandfinally_with_return_valueType() {
    System.out.println("get-tryandfinally_with_return_valueType-value:" + tryandfinally_with_return_valueType());
}

case5 结果

try:2
finally:3
get-tryandfinally_with_return_valueType-value:3

case5 原因分析

当 finally 中有 return 的时候,try 中的 return 会失效,在执行完 finally 的 return 之后,就不会再执行 try 中的 return。这种写法,编译是可以编译通过的,但是编译器会给予警告,所以不推荐在 finally 中写 return,这会破坏程序的完整性,而且一旦 finally 里出现异常,会导致 catch 中的异常被覆盖。

总结

1、finally 中的代码总会被执行。
2、当 try、catch 中有 return 时,也会执行 finally。return 的时候,要注意返回值的类型,是否受到 finally 中代码的影响。
3、finally 中有 return 时,会直接在 finally 中退出,导致 try、catch 中的 return 失效。IDE 中也会提示如下信息:'return' inside 'finally' block
Inspection info: Reports return statements inside of finally blocks. While occasionally intended, such return statements may mask exceptions thrown, and tremendously complicate debugging.

不建议的finally 块里面加 return的原因

public  static void main(String[] args) {
  System.out.println(test());
  System.out.println(test2());
}

public static String test() {
  try {
      throw new Exception("在try里面发生异常");
   } catch(Exception e) {
    // catch 中的异常被丢弃,并不会抛给main方法
     throw new Exception("在catch里面发生异常");
  } finally {
       return "我是finally";
   }
}

public static String test2() {
  try {
      //  try 的异常被丢弃,并不会抛给main方法
      throw new Exception("在try里面发生异常2");
   } finally {
       return "我是finally2";
   }
}

运行结果:

 我是finally
 我是finally2

可以看出在finally代码里面return,可能造成try或catch的异常被丢弃。A return statement inside a finally block will cause any exception that might be thrown in the try block to be discarded.

原文链接:https://mp.weixin.qq.com/s/oEquUNGQ4pkfVOh3Pr227A

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。