简介
interface类型在golang中是一种引用类型,多用在对一些相关或聚合类的function的一些抽象,实现类似于c++中的的多态。
类型定义
通用_type
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8// 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
str nameOff
ptrToThis typeOff
}
空interface定义
//空interface
type eface struct {
_type *_type
data unsafe.Pointer
}
非空interface定义
//非空interface
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for switches. _ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
常用场景
interface主要有2个用途,分别是用于层与层之间进行抽象和解耦;实现伪泛型(利用空inteface作为函数或参数),是golang实现多态(itab.fun表保存函数列表首地址)和反射的基础。
类型转换
指针类型转换
示例:
package main
import "fmt"
func main() {
var s Person = &Student{name: "test-name"}
s.sayHello("everyone")
}
type Person interface {
sayHello(name string) string
sayGoodbye(name string) string
}
type Student struct {
name string
}
//go:noinline
func (s *Student) sayHello(name string) string {
return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}
//go:noinline
func (s *Student) sayGoodbye(name string) string {
return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}
生成汇编
go tool compile -S -N -l main.go > main.s 2>&1
汇编代码解析
1)main函数主流程
"".main STEXT size=194 args=0x0 locals=0x50
0x0000 00000 (main.go:5) TEXT "".main(SB), ABIInternal, $80-0
0x0000 00000 (main.go:5) MOVQ (TLS), CX
0x0009 00009 (main.go:5) CMPQ SP, 16(CX)
0x000d 00013 (main.go:5) PCDATA $0, $-2
0x000d 00013 (main.go:5) JLS 184
0x0013 00019 (main.go:5) PCDATA $0, $-1
0x0013 00019 (main.go:5) SUBQ $80, SP
0x0017 00023 (main.go:5) MOVQ BP, 72(SP)
0x001c 00028 (main.go:5) LEAQ 72(SP), BP
0x0021 00033 (main.go:5) PCDATA $0, $-2
0x0021 00033 (main.go:5) PCDATA $1, $-2
0x0021 00033 (main.go:5) FUNCDATA $0, gclocals·7d2d5fca80364273fb07d5820a76fef4(SB)
0x0021 00033 (main.go:5) FUNCDATA $1, gclocals·95a7510f9a0f8c4e1ae4a25795da4a33(SB)
0x0021 00033 (main.go:5) FUNCDATA $2, gclocals·bfebb10a556cfca952c51fc0f9511921(SB)
0x0021 00033 (main.go:6) PCDATA $0, $1
0x0021 00033 (main.go:6) PCDATA $1, $0
0x0021 00033 (main.go:6) LEAQ type."".Student(SB), AX
0x0028 00040 (main.go:6) PCDATA $0, $0
0x0028 00040 (main.go:6) MOVQ AX, (SP)
0x002c 00044 (main.go:6) CALL runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
0x0031 00049 (main.go:6) PCDATA $0, $2
0x0031 00049 (main.go:6) MOVQ 8(SP), DI
0x0036 00054 (main.go:6) PCDATA $1, $1
0x0036 00054 (main.go:6) MOVQ DI, ""..autotmp_2+40(SP)
0x003b 00059 (main.go:6) MOVQ $9, 8(DI)
0x0043 00067 (main.go:6) PCDATA $0, $-2
0x0043 00067 (main.go:6) PCDATA $1, $-2
0x0043 00067 (main.go:6) CMPL runtime.writeBarrier(SB), $0
0x004a 00074 (main.go:6) JEQ 78
0x004c 00076 (main.go:6) JMP 170
0x004e 00078 (main.go:6) LEAQ go.string."test-name"(SB), AX
0x0055 00085 (main.go:6) MOVQ AX, (DI)
0x0058 00088 (main.go:6) JMP 90
0x005a 00090 (main.go:6) PCDATA $0, $1
0x005a 00090 (main.go:6) PCDATA $1, $0
0x005a 00090 (main.go:6) MOVQ ""..autotmp_2+40(SP), AX
0x005f 00095 (main.go:6) MOVQ AX, ""..autotmp_1+48(SP)
0x0064 00100 (main.go:6) PCDATA $0, $3
0x0064 00100 (main.go:6) PCDATA $1, $2
0x0064 00100 (main.go:6) LEAQ go.itab.*"".Student,"".Person(SB), CX
0x006b 00107 (main.go:6) PCDATA $0, $1
0x006b 00107 (main.go:6) MOVQ CX, "".s+56(SP)
0x0070 00112 (main.go:6) PCDATA $0, $0
0x0070 00112 (main.go:6) MOVQ AX, "".s+64(SP)
0x0075 00117 (main.go:7) MOVQ "".s+56(SP), AX
0x007a 00122 (main.go:7) TESTB AL, (AX)
0x007c 00124 (main.go:7) MOVQ 32(AX), AX
0x0080 00128 (main.go:7) PCDATA $0, $4
0x0080 00128 (main.go:7) PCDATA $1, $0
0x0080 00128 (main.go:7) MOVQ "".s+64(SP), CX
0x0085 00133 (main.go:7) PCDATA $0, $0
0x0085 00133 (main.go:7) MOVQ CX, (SP)
0x0089 00137 (main.go:7) PCDATA $0, $4
0x0089 00137 (main.go:7) LEAQ go.string."everyone"(SB), CX
0x0090 00144 (main.go:7) PCDATA $0, $0
0x0090 00144 (main.go:7) MOVQ CX, 8(SP)
0x0095 00149 (main.go:7) MOVQ $8, 16(SP)
0x009e 00158 (main.go:7) CALL AX
0x00a0 00160 (main.go:8) MOVQ 72(SP), BP
0x00a5 00165 (main.go:8) ADDQ $80, SP
0x00a9 00169 (main.go:8) RET
0x00aa 00170 (main.go:6) PCDATA $0, $-2
0x00aa 00170 (main.go:6) PCDATA $1, $-2
0x00aa 00170 (main.go:6) LEAQ go.string."test-name"(SB), AX
0x00b1 00177 (main.go:6) CALL runtime.gcWriteBarrier(SB)
0x00b6 00182 (main.go:6) JMP 90
0x00b8 00184 (main.go:6) NOP
0x00b8 00184 (main.go:5) PCDATA $1, $-1
0x00b8 00184 (main.go:5) PCDATA $0, $-2
0x00b8 00184 (main.go:5) CALL runtime.morestack_noctxt(SB)
0x00bd 00189 (main.go:5) PCDATA $0, $-1
0x00bd 00189 (main.go:5) JMP 0
0x0000 64 48 8b 0c 25 00 00 00 00 48 3b 61 10 0f 86 a5 dH..%....H;a....
0x0010 00 00 00 48 83 ec 50 48 89 6c 24 48 48 8d 6c 24 ...H..PH.l$HH.l$
0x0020 48 48 8d 05 00 00 00 00 48 89 04 24 e8 00 00 00 HH......H..$....
0x0030 00 48 8b 7c 24 08 48 89 7c 24 28 48 c7 47 08 09 .H.|$.H.|$(H.G..
0x0040 00 00 00 83 3d 00 00 00 00 00 74 02 eb 5c 48 8d ....=.....t..\H.
0x0050 05 00 00 00 00 48 89 07 eb 00 48 8b 44 24 28 48 .....H....H.D$(H
0x0060 89 44 24 30 48 8d 0d 00 00 00 00 48 89 4c 24 38 .D$0H......H.L$8
0x0070 48 89 44 24 40 48 8b 44 24 38 84 00 48 8b 40 20 H.D$@H.D$8..H.@
0x0080 48 8b 4c 24 40 48 89 0c 24 48 8d 0d 00 00 00 00 H.L$@H..$H......
0x0090 48 89 4c 24 08 48 c7 44 24 10 08 00 00 00 ff d0 H.L$.H.D$.......
0x00a0 48 8b 6c 24 48 48 83 c4 50 c3 48 8d 05 00 00 00 H.l$HH..P.H.....
0x00b0 00 e8 00 00 00 00 eb a2 e8 00 00 00 00 e9 3e ff ..............>.
0x00c0 ff ff ..
rel 5+4 t=17 TLS+0
rel 36+4 t=16 type."".Student+0
rel 45+4 t=8 runtime.newobject+0
rel 69+4 t=16 runtime.writeBarrier+-1
rel 81+4 t=16 go.string."test-name"+0
rel 103+4 t=16 go.itab.*"".Student,"".Person+0
rel 140+4 t=16 go.string."everyone"+0
rel 158+0 t=11 +0
rel 173+4 t=16 go.string."test-name"+0
rel 178+4 t=8 runtime.gcWriteBarrier+0
rel 185+4 t=8 runtime.morestack_noctxt+0
2)生成Student对象
执行流程:
2.1)获取Student类型地址,放到AX寄存器,然后放到栈顶,调用runtime.newobject创建对象,放到SB中,新对象地址保存在栈顶:8(SP)
2.2)0x0031~0x003b:创建临时对象autotmp_2放到40(SP),同时将对象的长度9设置到8(DI)
2.3)0x0043:判断runtime.writeBarrier(SB)是否=0,若等于0,则跳转到==78(0x004e)执行,在栈中分配对象,否则跳转到170(0x00aa)==执行,生成GC对象;
2.4)0x004e ~ 0x0055:获取字符串“test-name”在数据区地址,放到AX再设置到DI,即设置name字段;0x00aa ~ 0x00b1调用runtime.gcWriteBarrier(SB)生成GC监控对象;
0x0021 00033 (main.go:6) LEAQ type."".Student(SB), AX
0x0028 00040 (main.go:6) PCDATA $0, $0
0x0028 00040 (main.go:6) MOVQ AX, (SP)
0x002c 00044 (main.go:6) CALL runtime.newobject(SB) // 新生成对象放在当前SP栈顶,因为MOVQ AX, (SP) 占用8个字节,所以新生成的对象放在8(SP)
0x0031 00049 (main.go:6) PCDATA $0, $2
0x0031 00049 (main.go:6) MOVQ 8(SP), DI
0x0036 00054 (main.go:6) PCDATA $1, $1
0x0036 00054 (main.go:6) MOVQ DI, ""..autotmp_2+40(SP)
0x003b 00059 (main.go:6) MOVQ $9, 8(DI)
0x0043 00067 (main.go:6) PCDATA $0, $-2
0x0043 00067 (main.go:6) PCDATA $1, $-2
0x0043 00067 (main.go:6) CMPL runtime.writeBarrier(SB), $0
0x004a 00074 (main.go:6) JEQ 78
0x004c 00076 (main.go:6) JMP 170
0x004e 00078 (main.go:6) LEAQ go.string."test-name"(SB), AX
0x0055 00085 (main.go:6) MOVQ AX, (DI)
0x0058 00088 (main.go:6) JMP 90
0x005a 00090 (main.go:6) PCDATA $0, $1
...
0x00aa 00170 (main.go:6) PCDATA $0, $-2
0x00aa 00170 (main.go:6) PCDATA $1, $-2
0x00aa 00170 (main.go:6) LEAQ go.string."test-name"(SB), AX
0x00b1 00177 (main.go:6) CALL runtime.gcWriteBarrier(SB)
0x00b6 00182 (main.go:6) JMP 90
0x00b8 00184 (main.go:6) NOP
3)对象复制
执行流程:
3.1)0x005a ~ 0x005f生成临时变量autotmp_1
3.2)0x0064 ~ 0x006b生成一个itab.*"".Student,"".Person结构,防止在56(SP)
3.3)0x0070将变量autotmp_2+40(SP)设置在itab.*"".Student,"".Person后8个字节(65(SP)),一起组成一个iface结构,类型转换;
0x0058 00088 (main.go:6) JMP 90
0x005a 00090 (main.go:6) PCDATA $0, $1
0x005a 00090 (main.go:6) PCDATA $1, $0
0x005a 00090 (main.go:6) MOVQ ""..autotmp_2+40(SP), AX
0x005f 00095 (main.go:6) MOVQ AX, ""..autotmp_1+48(SP)
0x0064 00100 (main.go:6) PCDATA $0, $3
0x0064 00100 (main.go:6) PCDATA $1, $2
0x0064 00100 (main.go:6) LEAQ go.itab.*"".Student,"".Person(SB), CX
0x006b 00107 (main.go:6) PCDATA $0, $1
0x006b 00107 (main.go:6) MOVQ CX, "".s+56(SP)
0x0070 00112 (main.go:6) PCDATA $0, $0
0x0070 00112 (main.go:6) MOVQ AX, "".s+64(SP)
4)调用实例化对象Student函数
执行流程:
4.1)0x0075 ~ 0x007c将iface结构地址放置到AX寄存器,校验iface结构非空,32(AX)偏移到sayHello函数
4.2)0x0080 ~ 0x0085将*Student对象,压入栈顶 (SP)
4.3)0x0089 ~ 0x0095将字符串"everyone",压入栈顶8(SP)
4.4)0x009调用sayHello(CALL AX)函数
0x0075 00117 (main.go:7) MOVQ "".s+56(SP), AX
0x007a 00122 (main.go:7) TESTB AL, (AX)
0x007c 00124 (main.go:7) MOVQ 32(AX), AX
0x0080 00128 (main.go:7) PCDATA $0, $4
0x0080 00128 (main.go:7) PCDATA $1, $0
0x0080 00128 (main.go:7) MOVQ "".s+64(SP), CX
0x0085 00133 (main.go:7) PCDATA $0, $0
0x0085 00133 (main.go:7) MOVQ CX, (SP)
0x0089 00137 (main.go:7) PCDATA $0, $4
0x0089 00137 (main.go:7) LEAQ go.string."everyone"(SB), CX
0x0090 00144 (main.go:7) PCDATA $0, $0
0x0090 00144 (main.go:7) MOVQ CX, 8(SP)
0x0095 00149 (main.go:7) MOVQ $8, 16(SP)
0x009e 00158 (main.go:7) CALL AX
0x00a0 00160 (main.go:8) MOVQ 72(SP), BP
0x00a5 00165 (main.go:8) ADDQ $80, SP
0x00a9 00169 (main.go:8) RET
对象类型
示例golang代码:
package main
import "fmt"
func main() {
var s Person = Student{name: "test-name"}
s.sayHello("everyone")
}
type Person interface {
sayHello(name string) string
sayGoodbye(name string) string
}
type Student struct {
name string
}
//go:noinline
func (s Student) sayHello(name string) string {
return fmt.Sprintf("%v: Hello %v, nice to meet you.\n", s.name, name)
}
//go:noinline
func (s Student) sayGoodbye(name string) string {
return fmt.Sprintf("%v: Hi %v, see you next time.\n", s.name, name)
}
1)生成Student对象
在栈空间64(SP) 创建临时对象autotmp_1,调用runtime.convTString,新生成的对象,存放在16(SP)
0x0021 00033 (main_object.go:6) PCDATA $0, $0
0x0021 00033 (main_object.go:6) PCDATA $1, $0
0x0021 00033 (main_object.go:6) XORPS X0, X0
0x0024 00036 (main_object.go:6) MOVUPS X0, ""..autotmp_1+64(SP)
0x0029 00041 (main_object.go:6) PCDATA $0, $1
0x0029 00041 (main_object.go:6) LEAQ go.string."test-name"(SB), AX
0x0030 00048 (main_object.go:6) MOVQ AX, ""..autotmp_1+64(SP)
0x0035 00053 (main_object.go:6) MOVQ $9, ""..autotmp_1+72(SP)
0x003e 00062 (main_object.go:6) PCDATA $0, $0
0x003e 00062 (main_object.go:6) MOVQ AX, (SP)
0x0042 00066 (main_object.go:6) MOVQ $9, 8(SP)
0x004b 00075 (main_object.go:6) CALL runtime.convTstring(SB)
2)创建临时变量autotmp_2
0x0050 00080 (main_object.go:6) PCDATA $0, $1
0x0050 00080 (main_object.go:6) MOVQ 16(SP), AX
0x0055 00085 (main_object.go:6) MOVQ AX, ""..autotmp_2+40(SP)
3)创建iface结构
0x005a ~ 0x0061 生成一个go.itab."".Student,"".Person结构,并存放到48(SP)
0x0066 设置iface.data为*Student即16(SP)
0x005a 00090 (main_object.go:6) PCDATA $0, $2
0x005a 00090 (main_object.go:6) PCDATA $1, $1
0x005a 00090 (main_object.go:6) LEAQ go.itab."".Student,"".Person(SB), CX
0x0061 00097 (main_object.go:6) PCDATA $0, $1
0x0061 00097 (main_object.go:6) MOVQ CX, "".s+48(SP)
0x0066 00102 (main_object.go:6) PCDATA $0, $0
0x0066 00102 (main_object.go:6) MOVQ AX, "".s+56(SP)
4)调用实例化对象函数
执行流程:
4.1)0x006b ~ 0x0072 偏移到Student.sayHello函数地址,压入AX寄存器
4.2)0x0076 ~ 0x007b将iface.data压入栈顶
4.3)0x007f ~ 0x008b将sayHello参数“everyone”地址和字符串长度,压入栈顶
4.4)0x0094调用sayHello
0x006b 00107 (main_object.go:7) MOVQ "".s+48(SP), AX
0x0070 00112 (main_object.go:7) TESTB AL, (AX)
0x0072 00114 (main_object.go:7) MOVQ 32(AX), AX
0x0076 00118 (main_object.go:7) PCDATA $0, $3
0x0076 00118 (main_object.go:7) PCDATA $1, $0
0x0076 00118 (main_object.go:7) MOVQ "".s+56(SP), CX
0x007b 00123 (main_object.go:7) PCDATA $0, $0
0x007b 00123 (main_object.go:7) MOVQ CX, (SP)
0x007f 00127 (main_object.go:7) PCDATA $0, $3
0x007f 00127 (main_object.go:7) LEAQ go.string."everyone"(SB), CX
0x0086 00134 (main_object.go:7) PCDATA $0, $0
0x0086 00134 (main_object.go:7) MOVQ CX, 8(SP)
0x008b 00139 (main_object.go:7) MOVQ $8, 16(SP)
0x0094 00148 (main_object.go:7) CALL AX
类型断言
非空interface
1)断言为interface:
调用runtime.assertI2I2(SB),如果和目标interfacetype不同,则会按目标interfacetype生成一个新的interface返回
示例:
func main() {
var s Person = &Student{name: "test-name"}
v, ok := s.(Person)
if !ok {
fmt.Printf("%v\n", v)
}
}
2)断言为结构体
类型断言时,新构造一个*itab结构(参考汇编0x0094),和interface的*itab进行对比;
示例:
func main() {
var s Person = &Student{name: "test-name"}
v, ok := s.(*Student)
if !ok {
fmt.Printf("%v\n", v)
}
}
汇编:
0x0075 00117 (main.go:8) LEAQ go.itab.*"".Student,"".Person(SB), CX
0x007c 00124 (main.go:8) MOVQ CX, "".s+104(SP)
0x0081 00129 (main.go:8) MOVQ AX, "".s+112(SP)
0x0086 00134 (main.go:9) MOVQ $0, ""..autotmp_3+96(SP)
0x008f 00143 (main.go:9) MOVQ "".s+112(SP), AX
0x0094 00148 (main.go:9) LEAQ go.itab.*"".Student,"".Person(SB), CX
0x009b 00155 (main.go:9) NOP
0x00a0 00160 (main.go:9) CMPQ "".s+104(SP), CX
空interface
1)空interface类型断言,用eface.*_type和目标结构体的*_type对比;
备注:0x0069 ~ 0x0075生成了一个Student对象
示例:
func main() {
var s interface{} = &Student{name: "test-name"}
v, ok := s.(int)
if !ok {
fmt.Printf("%v\n", v)
}
}
汇编:
0x002f 00047 (main.go:8) XORPS X0, X0
0x0032 00050 (main.go:8) MOVUPS X0, ""..autotmp_8+136(SP)
0x003a 00058 (main.go:8) LEAQ ""..autotmp_8+136(SP), AX
0x0042 00066 (main.go:8) MOVQ AX, ""..autotmp_7+88(SP)
0x0047 00071 (main.go:8) TESTB AL, (AX)
0x0049 00073 (main.go:8) MOVQ $9, ""..autotmp_8+144(SP)
0x0055 00085 (main.go:8) LEAQ go.string."test-name"(SB), CX
0x005c 00092 (main.go:8) MOVQ CX, ""..autotmp_8+136(SP)
0x0064 00100 (main.go:8) MOVQ AX, ""..autotmp_3+96(SP)
0x0069 00105 (main.go:8) LEAQ type.*"".Student(SB), CX
0x0070 00112 (main.go:8) MOVQ CX, "".s+120(SP)
0x0075 00117 (main.go:8) MOVQ AX, "".s+128(SP)
对比_type:
0x007d 00125 (main.go:9) MOVQ "".s+120(SP), AX
0x0082 00130 (main.go:9) MOVQ "".s+128(SP), CX
0x008a 00138 (main.go:9) LEAQ type.int(SB), DX
0x0091 00145 (main.go:9) CMPQ DX, AX
0x0094 00148 (main.go:9) JEQ 155
0x0096 00150 (main.go:9) JMP 423
类型查询(Type Switchs)
非空接口
switch参数为非空接口
示例
func main() {
var s Person = &Student{name: "test-name"}
switch s.(type) {
case Person:
person := s.(Person)
person.sayHello("boy")
case *Student:
student := s.(*Student)
student.sayHello("boy")
case Student:
student := s.(Student)
student.sayHello("boy")
}
}
1)case 非空接口类型名,调用runtime.assertI2I2(),成功,则case匹配,进入case代码段
2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段
空接口
switch参数为空接口
示例
func main() {
var s interface{} = &Student{name: "test-name"}
switch s.(type) {
case Person:
person := s.(Person)
person.sayHello("everyone")
case *Student:
student := s.(*Student)
student.sayHello("everyone")
case Student:
student := s.(Student)
student.sayHello("everyone")
}
}
func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
t := e._type
if t == nil {
return
}
tab := getitab(inter, t, true)
if tab == nil {
return
}
r.tab = tab
r.data = e.data
b = true
return
}
1)case 非空接口类型名,调用runtime.assertE2I2(),通过getitab把eface.*_type封装成*itab;封装成功,则case匹配,进入case代码段
2)case 类型名,hash和itab均匹配,则case匹配,进入case代码段
备注:
1)fallthrough不适用于Type Switchs
2)hash值匹配只和字段和方法有关,和字段内具体值无关
性能消耗
1)构造iface过程
2)动态计算调用函数地址,通过iface.tab.fun表偏移查找;
场景对比
1)通过指针,实现多态,性能消耗很小,相对复杂业务逻辑,可忽略;
2)通过结构体,结构体在方法调用时,需要传值,拷贝参数,性能损耗较大;
建议:通过指针实现多态;