天天看点

java中finally和return的执行顺序(对比Go语言中defer)

Go语言中defer

finally

之前我们先说说Go语言中

defer

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:

java中finally和return的执行顺序(对比Go语言中defer)

这里先看一个例子:

package main

import "fmt"

func demo() int {
	i := 10
	defer func() {
		i = 20
	}()
	return i
}
func main() {
	fmt.Println(demo()) // 10
}
           

执行上面代码的时候相当于执行下面步骤:

  1. 返回值 = i = 10
  2. 执行

    defer

    中的函数:i = 20
  3. return

    返回值(此时的

    i

    为20,但是返回值是10)

假设对切片类型进行和上面类似的操作:

package main

import "fmt"

func demo() []string {
	var sli []string
	sli = append(sli, "hello")
	defer func() {
		sli = append(sli, "world")
	}()
	return sli
}
func main() {
	fmt.Println(demo()) // [hello]
}
           

同样,相当于执行以下操作:

  1. 返回值 =

    [hello]

  2. 执行

    defer

    中的函数:

    sli = [hello,world]

  3. return

    返回值

这里注意,每次执行

append

之后返回的切片地址都不同

假如我们自定义类型,然后对指针进行操作:

package main

import (
	"fmt"
)

type PersonA struct {
	name string
	age  int
}

func test() *PersonA {
	p := &PersonA{"小明", 18}
	defer func() {
		p.name = "小红"
	}()
	return p
}
func main() {
	fmt.Println(test()) // &{小红 18}
}
           

可见,运行结果为defer修改之后的值,事实上还是执行了以下步骤:

  1. 返回值 =

    p

    的地址
  2. 执行

    defer

    中的函数: 修改

    p

    的地址对应

    PersonA

    结构体
  3. 返回

    p

    的地址

这时候再打印p地址对应的对象,显然是我们已经修改过的对象。

java中的finally

java中的

finally

return

结合之后执行的顺序和Go语音中的

defer

类似

class MyExceptionDemo {
  public static void demo() {
    throw new RuntimeException();
  }

  public static int test() {
    int i = 10;
    try {
      demo();
      return i;
    } catch (Exception e) {
      i = 20;
      return i;
    } finally {
      i = 30;
    }
  }

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

这里相当于:

  1. 返回值 = i = 20
  2. 执行

    finally

    语句:i = 30
  3. 将返回值(20)返回

上面的是基本数据类型,如果是引用类型的话:

class ExceptionDemo {
  public static void demo() {
    throw new RuntimeException();
  }

  public static StringBuffer test() {
    StringBuffer sb = new StringBuffer();
    try {
      demo();
      return sb;
    } catch (final Exception e) {
      sb.append("do catch ");
      return sb;
    } finally {
      sb.append("do finally ");
    }
  }

  public static void main(final String[] args) {
    System.out.println(test()); // do catch do finally
  }
}
           

执行顺序:

  1. 返回值 =

    sb

    的地址
  2. 执行

    finally

    :修改

    sb

    对应地址的对象
  3. 返回

    sb

    的地址

finally

中有

return

语句的时候,

finally

中语句会覆盖

try

里的语句

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

public static String test2() {
    String str;
    try {
        str = "try";
        return str;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return "finally";
    }
}
           

finally使用过程中需要注意的地方

  • 不会执行

    finally

    的两种情况:
    1. try

      语句之前有

      return

      语句
    2. 程序中有

      System.exit();

      语句
  • 根据上面的例子,如果

    finally

    catch

    中都有

    return

    语句的话,在

    catch

    return

    语句没有返回的时候,

    finally

    中的

    return

    语句已经返回了,所以

    finally

    中的

    return

    语句会覆盖

    catch

    中的

    return

    语句

参考文章:

https://www.liwenzhou.com/posts/Go/09_function/

https://mp.weixin.qq.com/s/yrwYkEQuD1t1iWtOuWyZ6w