前言
本文將解釋Golang中interface的定義,用法谈跛,注意事項(xiàng)央拖,希望對(duì)大家的工作學(xué)習(xí)提供借鑒與幫助。
定義
interface定義
參考Golang Spec文檔(https://golang.org/ref/spec)卡睦,interface定義如下:
An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.
意思是說:接口類型指定了一個(gè)方法集(method set),這個(gè)方法集稱為該接口類型的接口漱抓。接口類型T的變量可以保存任意類型X的值,只要該類型X的方法集滿足是該接口類型T的超集恕齐。這樣的類型X可以說實(shí)現(xiàn)了接口類型T乞娄。未初始化的接口類型變量的值為nil。
Go語言里面显歧,聲明一個(gè)接口類型需要使用type關(guān)鍵字仪或、接口類型名稱、interface關(guān)鍵字和一組有{}括起來的方法聲明(method specification)士骤,這些方法聲明只有方法名范删、參數(shù)和返回值,不需要方法體拷肌。
如下我們聲明了Bird接口類型:
type Bird interface {
Twitter(name string) string
Fly(height int) bool
}
在一個(gè)接口類型中到旦,每個(gè)方法(method)必須名字非空且唯一(unique & non-blank)
Go語言沒有繼承的概念,那如果需要實(shí)現(xiàn)繼承的效果怎么辦巨缘?Go的方法是嵌入添忘。
接口類型支持嵌入(embedding)來實(shí)現(xiàn)繼承的效果。
一個(gè)接口類型T可以使用接口類型E的名字若锁,放在方法聲明的位置搁骑。稱為將接口類型E嵌入到接口類型T中,這樣會(huì)將接口類型E的全部方法(公開的,私有的)添加到接口類型T仲器。
例如:
type ReadWriter interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Locker interface {
Lock()
Unlock()
}
type File interface {
ReadWriter // same as adding the methods of ReadWriter
Locker // same as adding the methods of Locker
Close()
}
type LockedFile interface {
Locker
File // illegal: Lock, Unlock not unique
Lock() // illegal: Lock not unique
}
在java中煤率,通過類來實(shí)現(xiàn)接口。一個(gè)類需要在聲明通過implements顯示說明實(shí)現(xiàn)哪些接口乏冀,并在類的方法中實(shí)現(xiàn)所有的接口方法蝶糯。Go語言沒有類煤辨,也沒有implements,如何來實(shí)現(xiàn)一個(gè)接口呢端三?這里就體現(xiàn)了Go與別不同的地方了郊闯。
首先团赁,Go語言沒有類但是有struct欢摄,通過struct來定義模型結(jié)構(gòu)和方法怀挠。
其次害捕,Go語言實(shí)現(xiàn)一個(gè)接口并不需要顯示聲明尝盼,而是只要你實(shí)現(xiàn)了接口中的所有方法就認(rèn)為你實(shí)現(xiàn)了這個(gè)接口。這稱之為Duck typing裁赠。
method set定義
Golang Spec中對(duì)于Method Set的定義如下:
https://golang.org/ref/spec#Method_sets
A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type
T
consists of all methods declared with receiver typeT
. The method set of the corresponding pointer type*T
is the set of all methods declared with receiver*T
orT
(that is, it also contains the method set ofT
). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.
The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
意思是說:
一個(gè)類型可能有相關(guān)的方法集祖娘。接口類型的方法集就是其接口。
其他非接口類型T的方法集是所有receiver type
為類型T
的方法菇夸。
類型T相應(yīng)的指針類型*T
的方法集是所有receiver type為*T
或T
的方法庄新。
其他的類型方法集為空择诈。
在一個(gè)方法集中羞芍,每個(gè)方法名字唯一且不為空荷科。
一個(gè)類型的方法集決定了該類型可以使實(shí)現(xiàn)的接口畏浆,以及使用該類型作為receiver type
可以調(diào)用的方法刻获。
Stackoverflow針對(duì)上述晦澀的描述有非常精辟的總結(jié):
https://stackoverflow.com/questions/33587227/golang-method-sets-pointer-vs-value-receiver
- If you have a
*T
you can call methods that have a receiver type of*T
as well as methods that have a receiver type ofT
(the passage you quoted, Method Sets).- If you have a
T
and it is addressable you can call methods that have a receiver type of*T
as well as methods that have a receiver type ofT
, because the method callt.Meth()
will be equivalent to(&t).Meth()
(Calls).- If you have a
T
and it isn't addressable, you can only call methods that have a receiver type ofT
, not*T
.- If you have an interface
I
, and some or all of the methods inI
's method set are provided by methods with a receiver of*T
(with the remainder being provided by methods with a receiver ofT
), then*T
satisfies the interfaceI
, butT
doesn't. That is because*T
's method set includesT
's, but not the other way around (back to the first point again).
In short, you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same. However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won't be valid.
interface示例
package main
import "fmt"
// 聲明Bird接口類型
type Bird interface {
Twitter() string
Fly(height int) bool
}
// 聲明Chicken接口類型佑颇,該接口內(nèi)嵌Bird接口類型
type Chicken interface {
Bird
Walk()
}
// 聲明Swallow結(jié)構(gòu)體
type Swallow struct {
name string
}
// receiver type為*Sparrow(pointer type)的method set包括方法:Twitter(), Fly()
func (this *Swallow) Twitter() string {
fmt.Println(this.name + " Twitter")
return this.name
}
func (this *Swallow) Fly(height int) bool {
fmt.Println(this.name + " Fly")
return true
}
// receiver type為Swallow(value type)的method set包括方法:Walk()
func (this Swallow) Walk() {
fmt.Println(this.name + " Walk")
return
}
func BirdAnimation(bird Bird, height int) {
fmt.Printf("BirdAnimation: %T\n", bird)
bird.Fly(height)
bird.Twitter()
}
func ChickenAnimation(chicken Chicken) {
fmt.Printf("ChickenAnimation: %T\n", chicken)
chicken.Walk()
chicken.Twitter()
}
func main() {
swallow := &Swallow{name: "swallow"}
// 由于*Swallow實(shí)現(xiàn)了Bird接口類型的所有方法,所以我們可以將*Swallow類型的變量swallow賦值給Bird interface type變量bird
bird := swallow
BirdAnimation(bird, 200)
BirdAnimation(swallow, 100)
var chicken Chicken
swallow2 := Swallow{}
chicken = &swallow2
// variable swallow2's type is Swallow, Swallow's method set is Walk(),
// chicken's type is Chicken, Chicken's method set is Twitter(), Fly(), Walk()
// 一個(gè)指針類型(pointer type)的方法列表必然包含所有接收者為指針接收者(pointer receiver method)的方法,
// 一個(gè)非指針類型(value type)的方法列表也包含所有接收者為非指針類型(value receiver method)的方法
// Compile error for chicken = sparrow2,
// cannot use sparrow2 (type Sparrow) as type Chicken in assignment:
// Sparrow does not implement Chicken (Fly method has pointer receiver)
ChickenAnimation(chicken)
}
注意事項(xiàng)
- interface{} slice與interface{}的轉(zhuǎn)換
首先我們看看下面例程的代碼:
func printAll(values []interface{}) {
for _, val := range values {
fmt.Println(val)
}
}
func main(){
names := []string{"stanley", "david", "oscar"}
printAll(names)
}
執(zhí)行之后會(huì)報(bào)錯(cuò):
cannot use names (type []string) as type []interface {} in argument to printAll
下面的文章很好的解釋了為什么會(huì)有編譯錯(cuò)誤:
https://link.jianshu.com/?t=https://github.com/golang/go/wiki/InterfaceSlice
There are two main reasons for this.
The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear.
Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time.
Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long.
This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long.
The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.
正確的辦法是:
It depends on what you wanted to do in the first place.
If you want a container for an arbitrary array type, and you plan on changing back to the original type before doing any indexing operations, you can just use an interface{}. The code will be generic (if not compile-time type-safe) and fast.
If you really want a []interface{} because you'll be doing indexing before converting back, or you are using a particular interface type and you want to use its methods, you will have to make a copy of the slice.
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}