Go的接口總結

一、什麼是接口

  • 接口類型是一種抽象的類型,它描述了一系列方法的集合。
    • 接口約定:接口類型中定義的方法即爲約定,若一個具體類型實現了所有這些方法,則該類型就滿足該接口的約定,或者說它是這個接口類型的實例(實現了該接口)。
    • 可替換性(LSP里氏替換):滿足相同接口約定的類型之間可進行相互替換。例如:若一個方法的形參定義爲接口類型,那麼它可以接收任何滿足該接口約定的類型的實參。
    • 接口內嵌:接口類型可通過組合已有的接口來定義
    • io.Writer接口提供了所有的類型寫入bytes的抽象,包括文件類型,內存緩衝區,網絡鏈接,HTTP客戶端,壓縮工具,哈希等等;io.Reader可以代表任意可以讀取bytes的類型,io.Closer可以是任意可以關閉的值,例如一個文件或是網絡鏈接。還有fmt.Stringer接口等
    • 接口類型名一般以“er”結尾

二、什麼是接口值

  • 接口值:即接口變量的值,由兩個部分組成,一個具體的類型和那個類型的值。它們被稱爲接口的動態類型和動態值
    • 接口值的零值:動態類型type和對應的動態值value均爲nil,如var w io.Writer
    • 空接口值:當且僅當接口的動態類型type和對應的動態值value均爲nil時,才爲空接口值,此時它等於nil
    • 接口變量的賦值與調用過程:
      • 如w = os.Stdout,這個賦值過程調用了一個具體類型到接口類型的隱式轉換,這和顯式的使用io.Writer(os.Stdout)是等價的。這個接口值w的動態類型被設爲*os.Stdout指針的類型描述符,它的動態值持有os.Stdout的拷貝
      • 調用一個包含*os.File類型指針的接口值的Write方法,w.Write([]byte("hello")) ,使得(*os.File).Write方法被調用
    • 一個接口值可以持有任意大的動態值,不論動態值多大,接口值總是可以容下它
    • 接口值的可比較性:
      • 時刻記住:只能比較動態類型是可比較類型的接口值。
      • 如果接口值的動態類型是可比較的,那麼它們之間就可以使用==和!=來進行比較:兩個接口值相等僅當它們都是nil值或者它們的動態類型相同並且動態值也根據這個動態類型的==操作相等。
      • 如果接口值是可比較的,那麼它們可以用在map的鍵或者作爲switch語句的操作數
      • 非接口類型要麼是安全的可比較類型(如基本類型和指針)要麼是完全不可比較的類型(如切片,映射類型,和函數),但是在比較接口值或者包含了接口值的聚合類型時,我們必須要意識到潛在的panic。同樣的風險也存在於使用接口作爲map的鍵或者switch的操作數。
    • 注意:一個包含nil指針的接口不是nil接口(空接口),此時調用接口方法會發生panic錯誤。即一個接口值的動態類型type != nil,但動態值value == nil,此時的接口值 w != nil。(當把一個值爲nil的非接口類型的變量轉換爲接口類型時,即出現這種情況)
    • 技巧:使用接口時,直接聲明一個接口類型的變量,然後再對它賦值,之後使用該變量時,就可以直接把它和nil比較來判斷是否爲空接口

三、看下面示例

func test(w io.Writer) {
	if w != nil {
		fmt.Println("not nil")
	} else {
		fmt.Println("nil")
	}
}

func test1(w *bytes.Buffer) {
	if w != nil {
		fmt.Println("not nil")
	} else {
		fmt.Println("nil")
	}
}

func proc() {
	var b *bytes.Buffer // b 的接口類型爲 bytes.Buffer指針,值爲nil(指針指向的是nil)
	test(b)  => not nil // test 中 w 是一個接口類型(io.Writer是一個接口),但是當b作爲參數傳入時,w的動態類型被賦予了 *bytes.Buffer指針類型,並指向nil,不是一個空接口了,所以 != nil
	test1(b) => nil     // test1 中 w 是一個 *bytes.Buffer指針,b作爲參數傳入也是一個*bytes.Buffer指針類型,類型相同,這個時候 w != nil 比較的是 byte.Buffer這個指針的指向,由於b指針指向nil,所以 nil == nil

	fmt.Println("===================")

	var b2 = new(bytes.Buffer) // b2 的接口類型爲 bytes.Buffer指針,但是值不是nil,因爲new的時候會分配一段內存空間並將b2指向這段內存空間
	test(b2)  => not nil       // 同 test(b)
	test1(b2) => not nil       // 由於b2並不指向nil,所以 != nil

	fmt.Println("===================")
	b2 = nil                   // 將 b2 指向nil,就變成了和b一樣了
	test(b2)  => not nil
	test1(b2) => nil

	fmt.Println("===================")
	var b3 io.Writer       // 申明b3爲一個接口類型,同時由於沒有賦予動態類型,所以是一個空接口,== nil
	b3 = new(bytes.Buffer) // 將b3賦予動態接口類型爲bytes.Buffer,同時指向了一段新開闢的內存空間,不再是一個空接口
	test(b3)  => not nil   // test 中 w 被賦予爲b3,b3是有動態類型的,不是空接口,所以 != nil
	//test1(b3)            // 無法調用 test1,無法將io.Writer作爲*bytes.Buffer使用
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章