1. 程式人生 > 程式設計 >原來這才是 Go Interface

原來這才是 Go Interface

定義

  • Interface 是一個定義了方法簽名的集合,用來指定物件的行為,如果物件做到了 Interface 中方法集定義的行為,那就可以說實現了 Interface;

  • 這些方法可以在不同的地方被不同的物件實現,這些實現可以具有不同的行為;

  • interface 的主要工作僅是提供方法名稱簽名,輸入引數,返回型別。最終由具體的物件來實現方法,比如 struct;

  • interface 初始化值為 nil;

  • 使用 type 關鍵字來申明,interface 代表型別,大括號裡面定義介面的方法簽名集合。

    type Animal interface {
    	Bark() string
    	Walk() string
    }
    複製程式碼

    如下,Dog 實現了 Animal 介面,所以可以用 Animal 的例項去接收 Dog的例項,必須是同時實現 Bark() 和Walk() 方法,否則都不能算實現了Animal介面。

    type Dog struct {
    	name string
    }
    
    func (dog Dog) Bark() string {
    	fmt.Println(dog.name + ":wan wan wan!")
    	return "wan wan wan"
    }
    
    func (dog Dog) Walk() string {
    	fmt.Println(dog.name + ":walk to park!")
    	return
    "walk to park" } func main() { var animal Animal fmt.Println("animal value is:",animal) //animal value is: <nil> fmt.Printf("animal type is: %T\n",animal) //animal type is: <nil> animal = Dog{"旺財"} animal.Bark() //旺財:wan wan wan! animal.Walk() //旺財:walk to park! fmt.Println("animal value is:"
    ,animal) //animal value is: {旺財} fmt.Printf("animal type is: %T\n",animal) //animal type is: main.Dog } 複製程式碼

nil interface

在上面的例子中,我們列印剛定義的 animal:

  • value為 nil
  • type 也為 nil

官方定義:Interface values with nil underlying values:

  • 只宣告沒賦值的interface 是nil interface,value和 type 都是 nil
  • 只要賦值了,即使賦了一個值為nil型別,也不再是nil interface
type I interface {
	Hello()
}

type S []int

func (i S) Hello() {
	fmt.Println("hello")
}
func main() {
	var i I
	fmt.Printf("1:i Type:%T\n",i)
	fmt.Printf("2:i Value:%v\n",i)

	var s S
	if s == nil {
		fmt.Printf("3:s Value%v\n",s)
		fmt.Printf("4:s Type is %T\n",s)
	}

	i = s
	if i == nil {
		fmt.Println("5:i is nil")
	} else {
		fmt.Printf("6:i Type:%T\n",i)
		fmt.Printf("7:i Value:%v\n",i)
	}
}
複製程式碼

output:

	1:i Type:<nil>
	2:i Value:<nil>
	3:s Value[]
	4:s Type is main.S
	6:i Type:main.S
	7:i Value:[]
複製程式碼

從結果看,初始化的變數 i 是一個 nil interface,當把值為 nil 的變數 s 賦值i後,i 不再為nil interface。
細心的同學,會發現一個細節,輸出的第3行

3:s Value[]
複製程式碼

明明,s的值是 nil,卻輸出的是一個[],這是由於 fmt使用反射來確定列印的內容,因為 s 的型別是slice,所以 fmt用 []來表示。

empty interface

Go 允許不帶任何方法的 interface,這種型別的 interface 叫 empty interface。所有型別都實現了 empty interface,因為任何一種型別至少實現了 0 個方法。
典型的應用場景是 fmt包的Println方法,它能支援接收各種不同的型別的資料,並且輸出到控制檯,就是interface{}的功勞。下面我們看下案例:

func Print(i interface{}) {
	fmt.Println(i)
}
func main() {
	var i interface{}
	i = "hello"
	Print(i)
	i = 100
	Print(i)
	i = 1.29
	Print(i)
}
複製程式碼

Print 方法的引數型別為 interface{},我們傳入 string,int,float等型別它都能接收。
雖然interface{}可以接收任何型別的引數,但是interface{}型別的 slice 是不是就可以接受任何型別的 slice。如下程式碼將會觸發 panic 錯誤,

var dataSlice []int = foo()
var interfaceSlice []interface{} = dataSlice
// cannot use dataSlice (type []int) as type []interface { } in assignment
複製程式碼

具體原因,官網 wiki(github.com/golang/go/w…) 有描述,大致含義是,導致錯誤是有兩個原因的:

  • []interface{} 並不是一個interface,它是一個slice,只是slice 中的元素是interface
  • []interface{} 型別的記憶體大小是在編譯期間就確定的(N*2),而其他切片型別的大小則為 N * sizeof(MyType),因此不發快速的將型別[]MyType分配給 []interface{}。

判斷 interface 變數儲存的是哪種型別

一個 interface 可被多種型別實現,有時候我們需要區分 interface 變數究竟儲存哪種型別的值?型別斷言提供對介面值的基礎具體值的訪問

t := i.(T)
複製程式碼

該語句斷言介面值i儲存的具體型別為T,並將T的基礎值分配給變數t。如果i儲存的值不是型別 T ,將會觸發 panic 錯誤。為了避免 panic 錯誤發生,可以通過如下操作來進行斷言檢查

t,ok := i.(T)
複製程式碼

斷言成功,ok 的值為 true,斷言失敗 t 值為T型別的零值,並且不會發生 panic 錯誤。

func main() {
	var i interface{}
	i = "hello"

	s := i.(string)
	fmt.Println(s)

	s,ok := i.(string)
	fmt.Println(s,ok)

	f,ok := i.(float64)
	fmt.Println(f,ok)

	i = 100
	t,ok := i.(int)
	fmt.Println(t,ok)

	t2 := i.(string) //panic
	fmt.Println(t2)
}
複製程式碼

Type switch

還有一種方便的方法來判斷 interface 變數的具體型別,那就是利用 switch 語句。如下所示:

func Print(i interface{}) {
	switch i.(type) {
	case string:
		fmt.Printf("type is string,value is:%v\n",i.(string))
	case float64:
		fmt.Printf("type is float32,i.(float64))
	case int:
		fmt.Printf("type is int,i.(int))
	}
}
func main() {
	var i interface{}
	i = "hello"
	Print(i)
	i = 100
	Print(i)
	i = 1.29
	Print(i)
}
複製程式碼

靈活高效的 interface 動態型別,使 Go 語言在保持強靜態型別的安全和高效的同時,也能靈活安全地在不同相容型別之間轉換

原文出於我的Github

參考

Golang print nil
InterfaceSlice