Golang中的接口

接口 interface 是一种类型,一种抽象类型。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type animal interface {
Sing()
}

type dog struct {
}

func (dog) Sing() {
fmt.Println("wangwangwang")
}

type cat struct {
}

func (cat) Sing() {
fmt.Println("miaomiaomiao")
}

func main() {
var a1 animal = dog{}
a1.Sing()
var a2 animal = cat{}
a2.Sing()
fmt.Println(a1 == a2) // false
a2 = dog{}
a2.Sing()
fmt.Println(a1 == a2) // true
}

接口函数可以分布在父类和子类之间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type biology interface {
Sing()
Run()
}

type animal struct {
}

type dog struct {
animal
}

func (animal) Run() {}

func (dog) Sing() {}

接口的值接受者和指针接受者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type animal interface {
Sing()
}

type dog struct {
}

func (dog) Sing() {
fmt.Println("wangwangwang")
}

type cat struct {
}

func (*cat) Sing() {
fmt.Println("miaomiaomiao")
}

对象的值类型或者指针类型实现了接口中的方法,就算该类型实现了接口。

接口在调用时跟对象方法一致,如果是作为值接受者,则传递值拷贝,如果是作为指针接受者,则传递指针的拷贝。

但是接口在赋值时,也就是作为接口的接收者,如果指针接受者实现了这个方法,则必须以指针变量赋值。如果值接受者实现了这个方法,可以将值或者指针作为接收者。

1
2
3
4
5
6
7
var a1 animal
a1 = dog{}
a1.Sing()
a1 = &dog{}
a1.Sing()
//a1 = cat{} // cannot use cat{} (value of type cat) as type animal in assignment:
//a1.Sing()

空接口

空接口本身

1
2
var y interface{}
fmt.Printf("%T %v\n", y, y)

输出

1
<nil> <nil>

空接口可以接受任意类型变量或者函数。

1
2
3
4
5
6
7
var x interface{}
x = "A"
fmt.Printf("%T %v\n", x, x)
x = 123
fmt.Printf("%T %v\n", x, x)
x = func() {}
fmt.Printf("%T %v\n", x, x)

输出

1
2
3
string A
int 123
func() 0x1002a0030

一个接口的值,简称接口值,由一个具体类型和类型的值两部分组成,这两部分分别成为接口的动态类型和动态值。

获取接口的类型和接口断言,获取接口类型的值的表达式是x.(T)

1
2
3
4
5
var x interface{}
x = "abc"
if i, ok := x.(string); ok {
fmt.Printf("%s", i)
}
1
2
3
4
5
6
7
8
var x interface{}
x = 123
switch x.(type) { // 此处x一定是一个interface类型,如果去掉x类型声明(var x interface{}),则会报错
case int:
fmt.Printf("%T %d\n", x, x)
case string:
fmt.Printf("%T %s\n", x, x)
}

接口和nil的比较

只有类型和值都为nil时,这个接口才为nil

1
2
var x interface{} = nil
fmt.Println(x == nil) // true

当一个接口显式的赋值为nil,此时这个接口的 type 和data 都等于 nil,接口 == nil

1
2
3
4
5
6
7
8
9
10
11
12
13
type name interface {
Name()
}

type people struct{}

func (p *people) Name() {}

func main() {
var x name = &people{}
fmt.Printf("%T %v\n", x, x) // *main.people &{}
fmt.Println(x == nil) // false
}

当一个接口被赋予一个nil指针,此时接口的data是nil,但是接口的 type不是,此时接口 != nil

接口断言类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type Flyer interface {
fly()
}

type Walker interface {
walk()
}

type Bird struct {
name string
}

type Pig struct {
name string
}

func (p Pig) walk() {
fmt.Printf("%s is walking\n", p.name)
}

func (b Bird) walk() {
fmt.Printf("%s is walking\n", b.name)
}

func (b Bird) fly() {
fmt.Printf("%s is flying\n", b.name)
}

func main() {
var w, f interface{} // w 和 f 都是接口
w = Pig{name: "pig"}
f = Bird{name: "bird"}
s := []interface{}{w, f}
for _, v := range s {
if b, ok := v.(Pig); ok { // 接口断言类型
b.walk()
}
if p, ok := v.(Bird); ok {
p.fly()
p.walk()
}
}
}

执行结果

1
2
3
pig is walking
bird is flying
bird is walking

类型断言接口

1
2
3
4
5
6
7
8
9
10
11
12
var p = Pig{name: "pig"}
var b = Bird{name: "bird"}
s := []interface{}{p, b}
for _, v := range s {
if f, ok := v.(Flyer); ok {
f.fly()
}

if f, ok := v.(Walker); ok {
f.walk()
}
}

输出

1
2
3
pig is walking
bird is flying
bird is walking

判断是否实现接口

在第三方包中,通常会有这样的代码

1
var _ I = T

是用来验证T类型是否实现了I方法

例如

1
2
3
4
5
6
7
8
9
type name interface {
Name()
}

type people struct {
string
}

func (p *people) Name() {}
1
2
3
var _ name = people{}
// 编译器报错:cannot use people{} (value of type people) as type name in variable declaration:
people does not implement name (Name method has pointer receiver)

空接口类型比较

空接口比较主要是类型是否可以比较。字符串、整形、bool、浮点(注意精确性)、复数、数组(长度类型都一样才能相等)都是可以比较的。

切片:

1
运行时报错:panic: runtime error: comparing uncomparable type []int

map

1
运行时报错:panic: runtime error: comparing uncomparable type map[int]int

channel

1
2
3
4
5
可以比较,比较的是内存地址
var a interface{} = make(chan int)
var b interface{} = make(chan int)
fmt.Printf("%v %v\n", a, b) // 0x1400010e060 0x1400010e0c0
fmt.Println(a == b) // false

结构体

1
2
内部元素能比较即可,如果内部有元素无法比较,则结构体无法比较
由于结构体内部有顺序,有名称,相等的结构体需要有相同的变量内容,变量名称,变量顺序

函数

1
2
3
4
5
函数底层是指针,但是不能比较,运行时报错
var a interface{} = func() {}
var b interface{} = func() {}
fmt.Printf("%v %v\n", a, b)
fmt.Println(a == b) // panic: runtime error: comparing uncomparable type func()

接口底层内部实现

接口的底层代码在src/runtime/runtime2.go

1
2
3
4
5
6
7
8
9
10
11
// 非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}

// 空接口
type eface struct {
_type *_type
data unsafe.Pointer
}

非空接口初始化的过程是初始化一个iface类型的结构

1
2
3
4
type iface struct {
tab *itab // 存放接口自身类型及方法指针信息
data unsafe.Pointer // 数据信息
}

iface的结构有两个指针字段

1
2
itab 用来存放接口自身类型,和绑定的实例类型,以及实例相关的函数指针
data 指向接口绑定的实力的副本,接口的初始化也是一种值拷贝

data指向具体的实例数据,传递个接口的是值类型,则data指向指的副本,如果传递的是指针类型,data执行的是指针的副本。无论是接口的转换还是函数调用,都是值传递。

itab的数据结构

1
2
3
4
5
6
7
type itab struct {
inter *interfacetype // 接口类型元信息的指针
_type *_type // 指向接口存放的具体类型元信息的指针,data指针指向的是该类型的值
hash uint32 // copy of _type.hash. Used for type switches. // 拷贝的_type.hash.,是类型的hash值,用于接口断言或类型查询快速访问
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. // 指向具体类型的方法
}

itab这个数据结构是非空接口实现动态调用的基础,itab的信息被编译器和链接器保存了下来,存放在可执行文件的只读存储段中。itab存放在竞态分配的存储空间中,不受GC的限制,内存不会被回收。

_type的数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type _type struct {
size uintptr // 大小
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32 // hash
tflag tflag // 类型的特征标记
align uint8 // _type作为整体交量存放时的对齐字节数
fieldAlign uint8 // 当前结构体字段的对齐字节数
kind uint8 // 基础类型枚举值和反射中的Kind一致,kind决定了如何解析该类型,例如bool,string等
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个类型是否相等的函数
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte // GC相关信息
str nameOff
ptrToThis typeOff
}

Go是强类型语言,编译器需要为每一个类型维护类型的元信息,也就是_type,其他字段都是以_type为内嵌字段封装而成的结构体

接口的类型元信息的数据结构

1
2
3
4
5
type interfacetype struct {
typ _type // 类型
pkgpath name // 接口所属包的名字信息,名称和描述信息
mhdr []imethod // 接口的方法
}
1
2
3
4
type imethod struct {
name nameOff // 偏移量
ityp typeOff
}

ifaceeface的区别:

eface是没有任何方法集的接口,所以eface内不需要维护和动态内存分配相关的数据结构itabeface只关心存放的具体类型是什么,具体类型的值是什么。eface是不包含任何方法的空接口。

1
2
3
4
5
6
7
8
9
10
11
// 非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}

// 空接口
type eface struct {
_type *_type
data unsafe.Pointer
}

由于空接口没有方法,所以任意类型都实现了空接口,因此空接口可以存储任意类型的数值。

基础类型

Go的数据类型都是在_type的基础上,增加一些额外的字段来进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type arraytype struct {
typ _type
elem *_type
slice *_type
len uintptr
}

type chantype struct {
typ _type
elem *_type
dir uintptr
}

type slicetype struct {
typ _type
elem *_type
}

更多类型,例如funtypeptrtype都可在go/src/runtime/type.go中查看。

类型别名和类型声明

1
2
type S string      // 声明一种新的类型,S不是stiring类型
type S = string // 声明一种别名,S就是stirng

在声明类型的情况下

1
2
3
4
type S string
s1 := "abc"
var s2 S = "def"
fmt.Println(s1 == s2) // invalid operation: s1 == s2 (mismatched types string and S)

在起别名的情况下

1
2
3
4
type S = string
s1 := "abc"
var s2 S = "def"
fmt.Println(s1 == s2) // true

函数实现接口

函数实现接口的核心是函数实现接口中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Invoker interface {
Call(interface{})
}

type Struct struct {
}

func (s *Struct) Call(p interface{}) { // 接口体实现方法
fmt.Println("from struct", p)
}

type FuncCaller func(interface{})

func (f FuncCaller) Call(p interface{}) { // 函数实现方法,实现Call方法中调用具体函数
f(p)
}

func main() {
var invoker Invoker
s := new(Struct)
invoker = s
invoker.Call("hello")

invoker = FuncCaller(func(i interface{}) {
fmt.Println("function call ", i)
})
invoker.Call("hello")
}

接口动态类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type name interface {
Name()
}

type people struct {
string
}

func (p *people) Name() {}

var x name // 变量x是一个接口,类型和值都是nil
fmt.Println(x == nil) // true
fmt.Printf("%T %v\n", x, x)
var y *people // 变量y是一个指针变量,值是0x0
fmt.Println(y == nil) // true
fmt.Printf("%T %v\n", y, y)
x = y // 将y赋值给x,此时x有类型,值依然是nil
fmt.Println(x == nil) // false
fmt.Printf("%T %v\n", x, x)
var z = new(people) // 变量z初始化people类型,返回这个类型的地址
fmt.Println(z == nil) // false
fmt.Printf("%T %v\n", z, z)
x = z // 将变量z的类型和值赋值给x
fmt.Println(x == y) // false,虽然类型一样,但是指向的底层数据不是同一个,指针不想等
fmt.Printf("%T %v\n", x, x)

因此,实现了error接口,但是不能将errornil直接对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type MyError struct {
}

func (e MyError) Error() string {
return "my error"
}

func Process() error {
var err *MyError = nil
return err
}

func main() {
err := Process()
fmt.Println(err) // 返回的err是nil
fmt.Println(err == nil) // 但是err和nil不想等,这是因为err中有类型
}

打印动态地址和值

动态地址和值可以通过unsafe包获取,通过interface的组成可以看到是由两个指针组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type eface struct {
itype uintptr
data uintptr
}

var a interface{} = nil // type: nil data: nil
var b interface{} = (*int)(nil) // type: *int data: nil
x := 5
var c interface{} = &x // type: *int data: 5
ia := *(*eface)(unsafe.Pointer(&a)) // 按照eface的存储方式取值
ib := *(*eface)(unsafe.Pointer(&b))
ic := *(*eface)(unsafe.Pointer(&c))
fmt.Println(ic) // {4377462432 1374389948104} 类型和值
ia.data = ic.data
ib.data = ic.data
fmt.Println(ia) // {0 1374389948104} 值赋值,类型为0
fmt.Println(ib) // {4377462432 1374389948104} 类型和值都有
fmt.Println(*(*int)(unsafe.Pointer(ic.data))) // 5