go 语言 interface{} 的易错点

一,interface 介绍编程

    若是说 goroutine 和 channel 是 go 语言并发的两大基石,那 interface 就是 go 语言类型抽象的关键。在实际项目中,几乎全部的数据结构最底层都是接口类型。提及 C++ 语言,咱们当即能想到是三个名词:封装、继承、多态。go 语言虽然没有严格意义上的对象,但经过 interface,能够说是实现了多态性。(由以组合结构体实现了封装、继承的特性)数据结构

    go 语言中支持将 method、struct、struct 中成员定义为 interface 类型,使用 struct 举一个简单的栗子并发

 1 package main
 2 
 3 type animal interface {
 4     Move()
 5 }
 6 
 7 type bird struct{}
 8 
 9 func (self *bird) Move() {
10     println("bird move")
11 }
12 
13 type beast struct{}
14 
15 func (self *beast) Move() {
16     println("beast move")
17 }
18 
19 func animalMove(v animal) {
20     v.Move()
21 }
22 
23 func main() {
24     var a *bird
25     var b *beast
26     animalMove(a) // bird move
27     animalMove(b) // beast move
28 }

 

    使用 go 语言的 interface 特性,就能实现多态性,进行泛型编程。函数

二,interface 原理ui

   若是没有充分了解 interface 的本质,就直接使用,那最终确定会踩到很深的坑,要用就先要了解,先来看看 interface 源码this

 1 type eface struct {
 2     _type *_type
 3     data  unsafe.Pointer
 4 }
 5  
 6 type _type struct {
 7     size       uintptr // type size
 8     ptrdata    uintptr // size of memory prefix holding all pointers
 9     hash       uint32  // hash of type; avoids computation in hash tables
10     tflag      tflag   // extra type information flags
11     align      uint8   // alignment of variable with this type
12     fieldalign uint8   // alignment of struct field with this type
13     kind       uint8   // enumeration for C
14     alg        *typeAlg  // algorithm table
15     gcdata    *byte    // garbage collection data
16     str       nameOff  // string form
17     ptrToThis typeOff  // type for pointer to this type, may be zero
18 }

 

    能够看到 interface 变量之因此能够接收任何类型变量,是由于其本质是一个对象,并记录其类型和数据块的指针。(其实 interface 的源码还包含函数结构和内存分布,因为不是本文重点,有兴趣的同窗能够自行了解)spa

三,interface 判空的坑指针

    对于一个空对象,咱们每每经过 if v == nil 的条件语句判断其是否为空,但在代码中充斥着 interface 类型的状况下,不少时候判空都并非咱们想要的结果(其实了解或聪明的同窗从上述 interface 的本质是对象已经知道我想要说的是什么)code

 1 package main
 2 
 3 type animal interface {
 4     Move()
 5 }
 6 
 7 type bird struct{}
 8 
 9 func (self *bird) Move() {
10     println("bird move")
11 }
12 
13 type beast struct{}
14 
15 func (self *beast) Move() {
16     println("beast move")
17 }
18 
19 func animalMove(v animal) {
20     if v == nil {
21         println("nil animal")
22     }
23     v.Move()
24 }
25 
26 func main() {
27     var a *bird   // nil
28     var b *beast  // nil
29     animalMove(a) // bird move
30     animalMove(b) // beast move
31 }

 

    仍是刚才的栗子,其实在 go 语言中 var a *bird 这种写法,a 只是声明了其类型,但并无申请一块空间,因此这时候 a 本质仍是指向空指针,但咱们在 aminalMove 函数进行判空是失败的,而且下面的 v.Move() 的调用也是成功的,本质的缘由就是由于 interface 是一个对象,在进行函数调用的时候,就会将 bird 类型的空指针进行隐式转换,转换成实例的 interface animal 对象,因此这时候 v 其实并非空,而是其 data 变量指向了空。这时候看着执行都正常,那什么状况下坑才会绊倒咱们呢?只须要加一段代码orm

 1 package main
 2 
 3 type animal interface {
 4     Move()
 5 }
 6 
 7 type bird struct {
 8     name string
 9 }
10 
11 func (self *bird) Move() {
12     println("bird move %s", self.name) // panic
13 }
14 
15 type beast struct {
16     name string
17 }
18 
19 func (self *beast) Move() {
20     println("beast move %s", self.name) // panic
21 }
22 
23 func animalMove(v animal) {
24     if v == nil {
25         println("nil animal")
26     }
27     v.Move()
28 }
29 
30 func main() {
31     var a *bird   // nil
32     var b *beast  // nil
33     animalMove(a) // panic
34     animalMove(b) // panic
35 }

 

    在代码中,咱们给派生类添加 name 变量,并在函数的实现中进行调用,就会发生 panic,这时候的 self 实际上是 nil 指针。因此这里坑就出来了。有些人以为这类错误谨慎一些仍是能够避免的,那是由于咱们是正向思惟去代入接口,但若是反向编程就容易形成很难发现的 bug

 1 package main
 2 
 3 type animal interface {
 4     Move()
 5 }
 6 
 7 type bird struct {
 8     name string
 9 }
10 
11 func (self *bird) Move() {
12     println("bird move %s", self.name)
13 }
14 
15 type beast struct {
16     name string
17 }
18 
19 func (self *beast) Move() {
20     println("beast move %s", self.name)
21 }
22 
23 func animalMove(v animal) {
24     if v == nil {
25         println("nil animal")
26     }
27     v.Move()
28 }
29 
30 func getBirdAnimal(name string) *bird {
31     if name != "" {
32         return &bird{name: name}
33     }
34     return nil
35 }
36 
37 func main() {
38     var a animal
39     var b animal
40     a = getBirdAnimal("big bird")
41     b = getBirdAnimal("") // return interface{data:nil}
42     animalMove(a) // bird move big bird
43     animalMove(b) // panic
44 }

 

    这里咱们看到经过函数返回实例类型指针,当返回 nil 时,由于接收的变量为接口类型,因此进行了隐性转换再次致使了 panic(这类反向转换很难发现)。

    那咱们如何处理上述这类问题呢。我这边整理了三个点

    1,充分了解 interface 原理,使用过程当中须要谨慎当心

    2,谨慎使用泛型编程,接收变量使用接口类型,也须要保证接口返回为接口类型,而不该该是实例类型

    3,判空是使用反射 typeOf 和 valueOf 转换成实例对象后再进行判空

相关文章
相关标签/搜索