finally与return纠缠不清

finally与return纠缠不清

在常规认知中我们都知道finally关键字的作用是为了无论程序是否抛出异常都需要执行的代码声明,但是在搭配return使用时就开始逐渐模糊,逐步拉扯,逐渐抓狂。

前言

我们都知道finally中的代码是在return之后执行的,那么在return语句中对最终返回的结果进行了逻辑处理,finally中的代码还会生效吗?我们看如下代码:

public static int finallyTest(int x){
  try{
    return x+2;
  }finally {
    x++;
  }
}

如果我们传入x=1,那么最终返回的结果是多少呢?是3?是2?还是1?

同样的还有如下类似代码:

public static String getString(){
  String str = "abc";
  try{
    str += "def";
    return str;
  }catch (Exception e){
    e.printStackTrace();
  }finally {
    str += "ghi";
    str = null;
  }
  return str;
}

返回的字符串是多少呢?是abcdef?是abcghi?是abcdefghi?还是null?

以上两段代码看起来非常简单,可能大部分人不会混淆;但是不能完全理解的话在实际开发中由于方法之间代码量较大很容易就陷入其中,导致最终很难排查的系统BUG。

场景罗列

我们先罗列一下finally与return在使用过程中的使用场景,方便我们有一个系统认识

1、正常情况下try中包含return,finally中无return

如前言中包含的两种场景,对于第一种场景返回的x=3;对于第二种场景返回的str=“abcdef”

2、正常情况下try中包含return,finally中有return

将之前例子中finally中添加return关键字

public static String getString(){
  String str = "abc";
  try{
    str += "def";
    return str;
  }catch (Exception e){
    e.printStackTrace();
  }finally {
    str += "ghi";
    return str;
  }
}

此时finally中的return就会覆盖try中的return,返回值变成了"abcdefghi"

3、发生异常时catch中包含return
public static String getString(){
  String str = "abc";
  try{
    str += "def";
    if(str.length() >0 )throw new Exception("发生了异常");
    return str;
  }catch (Exception e){
    e.printStackTrace();
    return str;
  }finally {
    str += "ghi";
    str = null;
  }
}

此时retrun效果等同于try中的return,返回值为"abcdef"

4、发生异常时catch中无return
public static String getString(){
  String str = "abc";
  try{
    str += "def";
    if(str.length() >0 )throw new Exception("发生了异常");
    return str;
  }catch (Exception e){
    e.printStackTrace();
  }finally {
    str += "ghi";
  }
  return str;
}

此时返回值变成了"abcdefghi"

通过执行上边各场景的代码会发现只要return不在finally中,那么写在finally中的代码其实是无效的;这与我们的认知就出现了偏差,为什么会出现这样的现象?

我们将操作的对象换成Map再来测试一下:

5、以上四个场景使用Map进行验证
public static Map<String,String> getMap(){
  Map<String,String> map = new HashMap<>();
  map.put("KEY","INIT");
  try{
    map.put("KEY","try");
    if(map.size() > 0)throw new Exception("发生了异常");
    return map;
  }catch (Exception e){
    e.printStackTrace();
    map.put("KEY","catch");
  }finally {
    map.put("KEY","finally");
    map = null;
  }
  return map;
}

我们得到了如下结果:

1、正常情况下try中包含return,finally中无return  ----{KEY,finally}
2、正常情况下try中包含return,finally中有return  ----null
3、发生异常时catch中包含return                  ----{KEY,finally}
4、发生异常时catch中无return                    ----null

针对Map的测试我们看到了和我们预期相符的输出结果。

问题出现

1、为什么对于不同的数据类型会出现不同的情况?

针对基本数据类型在finally中对其进行修改是无效的,而引用数据类型修改可以生效;但是String类型是一个特例,因为String不是基本数据类型但是修改也是无效的。

2、finally中对于对象赋值null的操作为什么有些场景生效,而有些场景不生效?

null对象是一个特殊的对象,为对象赋值null代表了该对象不再存在。结合问题1会发现,在finally中对对象的更改不会生效,而对对象引用的修改则会生效。

总结

我们首先要知道return关键字的含义,其用于跳出当前方法,指向返回值指针(指针、指针、指针 …),即返回对象的内存地址。因此finally的操作能够更改的仅仅是该内存地址所存储的对象值,而不能更改该内存地址。

函数返回的步骤:
  • 先记录return指针位置,即此时返回值指针位置。
  • 然后执行finally语句块,
  • 然后返回return的指针位置。(至此函数执行结束)
  • 所以return指针位置已固定,是不可改变的事实,一切能改变return指针位置所指向的内容的行为均可改变返回值。而所有想要改变指针位置的操作均是无效的。

这样就可以解释基本数据类型每次修改均会导致内存地址的变更,因此在finally中的修改是不生效的;而String是一个特殊的引用数据类型,其存在在常量池中,对String对象执行“+=”操作相当于新创建一个String对象并将原String对象复制给新的String对象,这个时候也涉及到了内存地址的变更,因此也是不会生效的;对Map对象的修改则仅是内存地址中值的变动,不涉及到内存地址变更,因此修改是生效的。如果将Map对象直接赋值null的操作则会导致内存地址的变动,此操作不会生效。

特别注意

在上边测试中为了展示不同场景,在finally中添加了return;但是在实际开发中严禁在finally中添加return关键字,这样的操作可能会导致程序出现异常时无法反馈到日志中,进而出现无法捕获的异常。

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值