Golang語言社群--理解 go interface 的 5 個關鍵點
大家好,我是社群主編彬哥,本篇文章是給大家轉載的關於Go語言中interface相關的。
1、interface 是一種型別
type I interface {
Get() int
}
首先 interface 是一種型別,從它的定義可以看出來用了 type 關鍵字,更準確的說 interface 是一種具有一組方法的型別,這些方法定義了 interface 的行為。
go 允許不帶任何方法的 interface ,這種型別的 interface 叫 empty interface。
如果一個型別實現了一個 interface 中所有方法,我們說型別實現了該 interface
2、interface 變數儲存的是實現者的值
//1 type I interface { Get() int Set(int) } //2 type S struct { Age int } func(s S) Get()int { return s.Age } func(s *S) Set(age int) { s.Age = age } //3 func f(i I){ i.Set(10) fmt.Println(i.Get()) } func main() { s := S{} f(&s) //4 }
這段程式碼在 #1 定義了 interface I,在 #2 用 struct S 實現了 I 定義的兩個方法,接著在 #3 定義了一個函式 f 引數型別是 I,S 實現了 I 的兩個方法就說 S 是 I 的實現者,執行 f(&s) 就完了一次 interface 型別的使用。
interface 的重要用途就體現在函式 f 的引數中,如果有多種型別實現了某個 interface,這些型別的值都可以直接使用 interface 的變數儲存。
s := S{}
var i I //宣告 i
i = &s //賦值 s 到 i
fmt.Println(i.Get())
不難看出 interface 的變數中儲存的是實現了 interface 的型別的物件值,這種能力是
3、如何判斷 interface 變數儲存的是哪種型別
一個 interface 被多種型別實現時,有時候我們需要區分 interface 的變數究竟儲存哪種型別的值,go 可以使用 comma, ok 的形式做區分 value, ok := em.(T):em 是 interface 型別的變數,T代表要斷言的型別,value 是 interface 變數儲存的值,ok 是 bool 型別表示是否為該斷言的型別 T。
if t, ok := i.(*S); ok {
fmt.Println("s implements I", t)
}
ok 是 true 表明 i 儲存的是 *S 型別的值,false 則不是,這種區分能力叫 Type assertions (型別斷言)。
如果需要區分多種型別,可以使用 switch 斷言,更簡單直接,這種斷言方式只能在 switch 語句中使用。
switch t := i.(type) {
case *S:
fmt.Println("i store *S", t)
case *R:
fmt.Println("i store *R", t)
}
4、空的 interface
interface{} 是一個空的 interface 型別,根據前文的定義:一個型別如果實現了一個 interface 的所有方法就說該型別實現了這個 interface,空的 interface 沒有方法,所以可以認為所有的型別都實現了 interface{}。如果定義一個函式引數是 interface{} 型別,這個函式應該可以接受任何型別作為它的引數。
func doSomething(v interface{}){
}
如果函式的引數 v 可以接受任何型別,那麼函式被呼叫時在函式內部 v 是不是表示的是任何型別?並不是,雖然函式的引數可以接受任何型別,並不表示 v 就是任何型別,在函式 doSomething 內部 v 僅僅是一個 interface 型別,之所以函式可以接受任何型別是在 go 執行時傳遞到函式的任何型別都被自動轉換成 interface{}。go 是如何進行轉換的,以及 v 儲存的值究竟是怎麼做到可以接受任何型別的,感興趣的可以看看 Russ Cox 關於 interface 的實現 。
既然空的 interface 可以接受任何型別的引數,那麼一個 interface{}型別的 slice 是不是就可以接受任何型別的 slice ?
func printAll(vals []interface{}) { //1
for _, val := range vals {
fmt.Println(val)
}
}
func main(){
names := []string{"stanley", "david", "oscar"}
printAll(names)
}
上面的程式碼是按照我們的假設修改的,執行之後竟然會報cannot use names (type []string) as type []interface {} in argument to printAll 錯誤,why?
這個錯誤說明 go 沒有幫助我們自動把 slice 轉換成 interface{} 型別的 slice,所以出錯了。go 不會對 型別是interface{} 的 slice 進行轉換 。為什麼 go 不幫我們自動轉換,一開始我也很好奇,最後終於在 go 的 wiki 中找到了答案 https://github.com/golang/go/wiki/InterfaceSlice 大意是 interface{} 會佔用兩個字長的儲存空間,一個是自身的 methods 資料,一個是指向其儲存值的指標,也就是 interface 變數儲存的值,因而 slice []interface{} 其長度是固定的N*2,但是 []T 的長度是N*sizeof(T),兩種 slice 實際儲存值的大小是有區別的(文中只介紹兩種 slice 的不同,至於為什麼不能轉換猜測可能是 runtime 轉換代價比較大)。
但是我們可以手動進行轉換來達到我們的目的。
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
5、interface 的實現者的 receiver 如何選擇
在我們上文的例子中呼叫 f 是 f(&s) 也就是 S 的指標型別,為什麼不能是 f(s) 呢,如果是 s 會有什麼問題?改成 f(s) 然後執行程式碼。
cannot use s (type S) as type I in argument to f:
S does not implement I (Set method has pointer receiver)
這個錯誤的意思是 S 沒有實現 I,哪裡出了問題?關鍵點是 S 中 set 方法的 receiver 是個 pointer *S 。
interface 定義時並沒有嚴格規定實現者的方法 receiver 是個 value receiver 還是 pointer receiver,上面程式碼中的 S 的 Set receiver 是 pointer,也就是實現 I 的兩個方法的 receiver 一個是 value 一個是 pointer,使用 f(s)的形勢呼叫,傳遞給 f 的是個 s 的一份拷貝,在進行 s 的拷貝到 I 的轉換時,s 的拷貝不滿足 Set 方法的 receiver 是個 pointer,也就沒有實現 I。go 中函式都是按值傳遞即 passed by value。
那反過來會怎樣,如果 receiver 是 value,函式用 pointer 的形式呼叫?
type I interface {
Get() int
Set(int)
}
type SS struct {
Age int
}
func (s SS) Get() int {
return s.Age
}
func (s SS) Set(age int) {
s.Age = age
}
func f(i I) {
i.Set(10)
fmt.Println(i.Get())
}
func main(){
ss := SS{}
f(&ss) //ponter
f(ss) //value
}
I 的實現者 SS 的方法 receiver 都是 value receiver,執行程式碼可以看到無論是 pointer 還是 value 都可以正確執行。
導致這一現象的原因是什麼?
如果是按 pointer 呼叫,go 會自動進行轉換,因為有了指標總是能得到指標指向的值是什麼,如果是 value 呼叫,go 將無從得知 value 的原始值是什麼,因為 value 是份拷貝。go 會把指標進行隱式轉換得到 value,但反過來則不行。
對於 receiver 是 value 的 method,任何在 method 內部對 value 做出的改變都不影響呼叫者看到的 value,這就是按值傳遞。
另一個說明上述現象的例子是這樣的來自 https://play.golang.org/p/TvR758rfre
package main
import (
"fmt"
)
type Animal interface {
Speak() string
}
type Dog struct {
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
}
//1
func (c *Cat) Speak() string {
return "Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
return "?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
return "Design patterns!"
}
func main() {
animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
1Cat 的 speak receiver 是 pointer,interface Animal 的 slice,Cat 的值是一個 value,同樣會因為 receiver 不一致而導致無法執行。