原來這才是 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
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 語言在保持強靜態型別的安全和高效的同時,也能靈活安全地在不同相容型別之間轉換