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

相關文章
相關標籤/搜索