panic

panic是个函数,有一个空接口类型的入参(可传任意类型,如字符串、error等等),无出参,作用类似于java中的throw new RuntimeException(msg),panic之后的代码不执行,程序会非零退出。

func main() {
    fmt.Println(1)
    panic("something wrong")
    fmt.Println(2)
}

只会打印1,不会打印2,main函数非零退出。

defer

defer是个关键字,后面跟一个函数。defer好像java中的finally块,即使程序panic了,defer后的函数也会执行。如果有多个defer及对应函数,则在后面的defer对应的函数会先执行,前面的defer对应的函数会后执行。

defer在匿名返回值函数和命名返回值函数中表现不同。

所谓匿名返回值函数,就是说在定义函数时,返回值只定义了形参,其实就是java中的方法定义,如下:

func returnValues() int {
    var result int
    defer func() {
        result++
        fmt.Println("defer returnValues")
    }()
    return result
}

所谓命名返回值函数,就是说在定义函数时,返回值不止定义了形参,还定义了一个变量,如下:

func namedReturnValues() (result int) {
    defer func() {
        result++
        fmt.Println("defer namedReturnValues")
    }()
    return result
}

匿名返回值函数,返回值不受defer后函数影响。而命名返回值函数,返回值受defer后函数影响。

recover

recover也是个函数,没有入参,出参是个空接口类型,具体返回什么类型取决于使用的场景:

如果recover()在defer后面的函数中调用,则如果goroutine panic了,则recover()函数返回panic函数的入参,同时panic会被压制住,相当于异常被catch住,程序不会非零退出了,否则返回nil。

如果recover()不是在defer后面的函数中调用,则返回nil,且不会压制panic。

defer和recover配合使用,可以达到try catch finally的效果

示例如下:

func MayPanic(i int) int {
    if i == 0 {
        panic("不能为0")
    } else if i < 0 {
        panic(errors.New("不能小于0"))
    }
    return 1
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println(r)
            fmt.Println(reflect.TypeOf(r))
        }
    }()
    //i := MayPanic(0)
    i := MayPanic(-1)
    fmt.Println(i)
}

上例中,recover()在defer后面的函数中调用,且协程因执行panic(errors.New("不能小于0"))而panic了,所以recover()函数返回error,error是由errors.New("不能小于0")生成的。main函数不会非零退出。

func main() {
    func() {
        if r := recover(); r == nil {
            fmt.Println(r)
            fmt.Println(reflect.TypeOf(r))
        }
    }()
    i := MayPanic(1)
    fmt.Println(i)
}

有牛人利用panic、defer、recover写出了使用方式与java相似的try catch finally,参考https://www.jianshu.com/p/74ab31191329

如下

package main

import (
    "log"
)

type ExceptionStruct struct {
    Try     func()
    Catch   func(Exception)
    Finally func()
}
type Exception interface{}

func Throw(up Exception) {
    panic(up)
}
func (ex ExceptionStruct) Do() {
    if ex.Finally != nil {
        defer ex.Finally()
    }
    if ex.Catch != nil {
        defer func() {
            if e := recover(); e != nil {
                ex.Catch(e)
            }
        }()
    }
    ex.Try()
}

func main() {
    log.Println("开始执行...")
    ExceptionStruct{
        Try: func() {
            log.Println("try...")
            Throw("发生了错误")
        },
        Catch: func(e Exception) {
            log.Println("exception=", e)
        },
        Finally: func() {
            log.Println("Finally...")
        },
    }.Do()
    log.Println("结束运行")
}

实测,效果跟java的try catch finally功能一模一样。在项目中使用时,可以把除main函数之外的代码抽出来,放到另一个go文件中,要用时引入这个go文件对应的package即可。

相关文章: