一,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 轉換成實例對象後再進行判空