現在的位置: 首頁 > 架構設計 > 正文

介面類型斷言和type-switch結構

2020年02月21日 架構設計 ⁄ 共 3425字 ⁄ 字型大小 評論關閉

  介面轉回成具體類型

  介面實例中可以存放各種實現了介面的類型實例,在有需要的時候,還可以通過ins.(Type)或ins.(*Type)的方式將介面實例ins直接轉回Type類型的實例。

  var i int = 30 var ins interface{} // 介面實例ins中保存的是int類型 ins = i

  x := ins.(int) // 介面轉回int類型的實例i println(x) //輸出30

  但注意,這時候的i和x在底層不是同一個對象,它們的地址是不同的。

  var i int = 30 var ins interface{}

  ins = i

  x := ins.(int) println("i addr: ",&i,"x addr: ",&x)

  輸出:

  0xc042049f68 0xc042049f60

  注意,介面實例轉回時,介面實例中存放的是什麼類型,才能轉換成什麼類型。同類型的值類型實例和指針類型實例不能互轉,不同類型更不能互轉。

  在不能轉換時,Golang將直接以Panic的方式終止程序。但可以處理轉換失敗時的panic,這時需要類型斷言,也即類型檢測。

  介面類型探測:類型斷言

  類型探測的方式和類型轉換的方式都是ins.(Type)和ins.(*Type)。當處於單個返回值上下文時,做的是類型轉換,當處於兩個返回值的上下文時,做的是類型探測。類型探測的第一個返回值是類型轉換之後的類型實例,第二個返回值是布爾型的ok返回值。

  // 如果ins保存的是值類型的Type,則輸出 if t, ok := ins.(Type); ok {

  fmt.Printf("%T\n", v)

  } // 如果ins保存的是指針類型的*Type,則輸出 if t, ok := ins.(*Type); ok {

  fmt.Printf("%T\n", v)

  } // 一個返回值的探測 t := ins.(Type)

  t := ins.(*Type)

  以下是一個例子:

  package main import "fmt" // Shaper 介面類型 type Shaper interface {

  Area() float64 } // Square struct類型 type Square struct {

  length float64 } // Square類型實現Shaper中的方法Area() func (s Square) Area() float64 { return s.length * s.length

  } func main() { var ins1, ins2 Shaper // 指針類型的實例 s1 := new(Square)

  s1.length = 3.0 ins1 = s1 if v, ok := ins1.(*Square); ok {

  fmt.Printf("ins1: %T\n", v)

  } // 值類型的實例 s2 := Square{4.0}

  ins2 = s2 if v, ok := ins2.(Square); ok {

  fmt.Printf("ins2: %T\n", v)

  }

  }

  上面兩個Printf都會輸出,因為它們的類型判斷都返回true。如果將ins2.(Square)改為ins2.(*Square),第二個Printf將不會輸出,因為ins2它保存的是值類型的實例。

  以下是輸出結果:

  ins1: *main.Square ins2: main.Square

  特別需要注意的是,ins必須明確是介面實例。例如,以下前兩種聲明是有效的,第三種推斷類型是錯誤的,因為它可能是介面實例,也可能是類型的實例副本。

  var ins Shaper // 正確 ins := Shaper(s1) // 正確 ins := s1 // 錯誤

  當ins不能確定是介面實例時,用它來進行測試,例如ins.(Square)將會報錯:

  invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)

  它說明了左邊的ins是非介面類型(non-interface type)。

  另一方面,通過介面類型斷言(ins.(Type)),如果Type是一個介面類型,就可以判斷介面實例ins中所保存的類型是否也實現了Type介面。例如:

  var r io.Read

  tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err

  }

  r = tty

  var w io.Writer

  w = r.(io.Writer)

  上面的r是io.Read介面的一個實例變數,它裡面保存的是tty和它的類型,即(tty, *os.File),然後斷言r的類型,探測它裡面的類型*File是否也實現了io.Writer介面,如果實現了,則保存到io.Writer介面的實例變數w中,這樣w實例也將保存(tty,*os.File)。

  由於任意內容都實現了空介面,所以,總是可以把一個介面實例無需通過任何斷言地賦值給一個空介面實例:

  var empty interface{} empty = w

  現在empty也保存了(tty,*os.File)。

  type Switch結構

  直接用if v,ok := ins.(Type);ok {}的方式做類型探測在探測類型數量多時不是很方便,需要重複寫if結構。

  Golang提供了switch...case結構用於做多種類型的探測,所以這種結構也稱為type-switch。這是比較方便的語法,比如可以判斷某介面如果是A類型,就執行A類型里的特有方法,如果是B類型,就執行B類型里的特有方法。

  用法如下:

  switch v := ins.(type) { case *Square: fmt.Printf("Type Square %T\n", v) case *Circle: fmt.Printf("Type Circle %T\n", v) case nil: fmt.Println("nil value: nothing to check?") default: fmt.Printf("Unexpected type %T", v)

  }

  其中ins.(type)中的小寫type是固定的詞語。

  以下是一個使用示例:

  package main import ( "fmt" ) // Shaper 介面類型 type Shaper interface {

  Area() float64 } // Circle struct類型 type Circle struct {

  radius float64 } // Circle類型實現Shaper中的方法Area() func (c *Circle) Area() float64 { return 3.14 * c.radius * c.radius

  } // Square struct類型 type Square struct {

  length float64 } // Square類型實現Shaper中的方法Area() func (s Square) Area() float64 { return s.length * s.length

  } func main() {

  s1 := &Square{3.3}

  whichType(s1)

  s2 := Square{3.4}

  whichType(s2)

  c1 := new(Circle)

  c1.radius = 2.3 whichType(c1)

  } func whichType(n Shaper) { switch v := n.(type) { case *Square:

  fmt.Printf("Type Square %T\n", v) case Square:

  fmt.Printf("Type Square %T\n", v) case *Circle:

  fmt.Printf("Type Circle %T\n", v) case nil:

  fmt.Println("nil value: nothing to check?") default:

  fmt.Printf("Unexpected type %T", v)

  }

  }

  上面的type-switch中,之所以沒有加上case Circle,是因為Circle只實現了指針類型的receiver,根據Method Set對介面的實現規則,只有指針類型的Circle示例才算是實現了介面Shaper,所以將值類型的示例case Circle放進type-switch是錯誤的。

抱歉!評論已關閉.