Golang interface 解析

前言

本文将解释Golang中interface的定义,用法,注意事项,希望对大家的工作学习提供借鉴与帮助。


定义

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.

意思是说:接口类型指定了一个方法集(method set),这个方法集称为该接口类型的接口。接口类型T的变量可以保存任意类型X的值,只要该类型X的方法集满足是该接口类型T的超集。这样的类型X可以说实现了接口类型T。未初始化的接口类型变量的值为nil。

Go语言里面,声明一个接口类型需要使用type关键字、接口类型名称、interface关键字和一组有{}括起来的方法声明(method specification),这些方法声明只有方法名、参数和返回值,不需要方法体。
如下我们声明了Bird接口类型:

type Bird interface {
    Twitter(name string) string
    Fly(height int) bool
}

在一个接口类型中,每个方法(method)必须名字非空且唯一(unique & non-blank)

Go语言没有继承的概念,那如果需要实现继承的效果怎么办?Go的方法是嵌入。
接口类型支持嵌入(embedding)来实现继承的效果。
一个接口类型T可以使用接口类型E的名字,放在方法声明的位置。称为将接口类型E嵌入到接口类型T中,这样会将接口类型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中,通过类来实现接口。一个类需要在声明通过implements显示说明实现哪些接口,并在类的方法中实现所有的接口方法。Go语言没有类,也没有implements,如何来实现一个接口呢?这里就体现了Go与别不同的地方了。
首先,Go语言没有类但是有struct,通过struct来定义模型结构和方法。
其次,Go语言实现一个接口并不需要显示声明,而是只要你实现了接口中的所有方法就认为你实现了这个接口。这称之为Duck typing。

method set定义

Golang Spec中对于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 type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). 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.
意思是说:
一个类型可能有相关的方法集。接口类型的方法集就是其接口。
其他非接口类型T的方法集是所有receiver type为类型T的方法。
类型T相应的指针类型*T的方法集是所有receiver type为*TT的方法。
其他的类型方法集为空。
在一个方法集中,每个方法名字唯一且不为空。

一个类型的方法集决定了该类型可以使实现的接口,以及使用该类型作为receiver type可以调用的方法。

Stackoverflow针对上述晦涩的描述有非常精辟的总结:
https://stackoverflow.com/questions/33587227/golang-method-sets-pointer-vs-value-receiver

  1. 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 of T (the passage you quoted, Method Sets).
  2. 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 of T, because the method call t.Meth() will be equivalent to (&t).Meth() (Calls).
  3. If you have a T and it isn't addressable, you can only call methods that have a receiver type of T, not *T.
  4. If you have an interface I, and some or all of the methods in I's method set are provided by methods with a receiver of *T (with the remainder being provided by methods with a receiver of T), then *T satisfies the interface I, but T doesn't. That is because *T's method set includes T'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接口类型,该接口内嵌Bird接口类型
type Chicken interface {
    Bird
    Walk()
}

// 声明Swallow结构体
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实现了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()

    // 一个指针类型(pointer type)的方法列表必然包含所有接收者为指针接收者(pointer receiver method)的方法,
    // 一个非指针类型(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)
}


注意事项

  1. interface{} slice与interface{}的转换

首先我们看看下面例程的代码:

func printAll(values []interface{}) { 
        for _, val := range values {    
                fmt.Println(val)
        }
}
func main(){
        names := []string{"stanley", "david", "oscar"}
        printAll(names)
}

执行之后会报错:
cannot use names (type []string) as type []interface {} in argument to printAll

下面的文章很好的解释了为什么会有编译错误:
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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,260评论 0 10
  • 一、如何挑选指数基金 1.格雷厄姆价值投资的三点:1.价值与价格的关系;2.能力圈;3安全边际 2.市盈率PE=市...
    定投小达人阅读 113评论 0 0
  • 5.29日课“公平理论”感悟: 治身莫先于孝,治国莫先于公。公平的力量还是很强大的,所以做管理=做公平。 我们都不...
    合肥李风丽阅读 163评论 2 3
  • 14 张立德看了看沈宏文的资料,所有的经历显然都是张立言的,再加上两个人张得那么像,一般人就很难认出来了。他似乎发...
    南王舍人阅读 431评论 0 0
  • 元旦本来计划回乐山,皮皮身体不适,又临时决定呆在成都,加上雾霾,只能选择窝家。 带爸爸去看病检查身体,没...
    liuxinamy阅读 318评论 1 0