Go语言中defer
说
finally
之前我们先说说Go语言中
defer
Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSP9ElT5tmeONTSU5UMRpXTwhnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2kTOzEzNwIjM1ITMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
这里先看一个例子:
package main
import "fmt"
func demo() int {
i := 10
defer func() {
i = 20
}()
return i
}
func main() {
fmt.Println(demo()) // 10
}
执行上面代码的时候相当于执行下面步骤:
- 返回值 = i = 10
- 执行
中的函数:i = 20defer
-
返回值(此时的return
为20,但是返回值是10)i
假设对切片类型进行和上面类似的操作:
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]
}
同样,相当于执行以下操作:
- 返回值 =
[hello]
- 执行
中的函数:defer
sli = [hello,world]
-
返回值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修改之后的值,事实上还是执行了以下步骤:
- 返回值 =
的地址p
- 执行
中的函数: 修改defer
的地址对应p
结构体PersonA
- 返回
的地址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
}
}
这里相当于:
- 返回值 = i = 20
- 执行
语句:i = 30finally
- 将返回值(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
}
}
执行顺序:
- 返回值 =
的地址sb
- 执行
:修改finally
对应地址的对象sb
- 返回
的地址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
- 在
语句之前有try
语句return
- 程序中有
语句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