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即可。