接口与反射
接口
从类型检查的过程来看, Go语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查, 类型实现接口时只需要实现接口中的全部方法, 不需要像java等编程语言一样显式声明.
Go语言使用runtime.iface表示带有一组方法的接口, 使用runtime.eface表示不带任何方法的interface{}.
type eface struct {
_type *_type
data unsafe.Pointer
}
type iface struct {
tab *itab
data unsafe.Pointer
}
runtime._type是Go语言类型的运行时表示, 包含了很多类型的元信息, 例如类型的大小、哈希、对齐以及种类等.
type itab struct {
inter *interfacetype
_type *_type
hash uint32
_ [4]byte
fun [1]uintptr
}
itab结构体是接口类型和具体类型的组合, 分别用inter和_type两个字段表示. hash是对_type.hash的拷贝, 当我们想将interface类型转换为具体类型时, 可以使用该字段快速判断目标类型和具体类型是否一致. fun是一个动态大小的数组, 它是一个用于动态派发的虚函数表, 存储了一组函数指针.
类型转换
在实现接口时有指针类型和结构体类型两种方式, 会导致go语言编译器生成不同的汇编代码, 进而影响最终的处理过程.
- 指针类型
package main
type Duck interface {
Quack()
}
type Cat struct {
Name string
}
//go:noinline
func (c *Cat) Quack() {
println(c.Name + " meow")
}
func main() {
var c Duck = &Cat{Name: "draven"}
c.Quack()
}
编译器将上述代码编译成汇编语言,将生成的汇编指令可以拆分成三部分:结构体Cat的初始化; 赋值出发的类型转换; 调用接口的方法Quack().
Cat的初始化过程:获取Cat结构体的类型指针, 并申请新的heap内存, 栈上的指针指向申请的这块堆内存.
类型转换: Duck的底层使用iface表示, 包含指向数据的指针和表示接口和结构体关系的tab字段共两个字段. Cat初始化过程中初始化了Cat的结构体指针, 只需要将编译期间生成的itab结构体复制到栈上.
调用接口方法: 编译期间会将一些需要动态派发的方法调用改写成对目标方法的直接调用, 以减少性能的额外开销.
- 结构体类型
package main
type Duck interface {
Quack()
}
type Cat struct {
Name string
}
//go:noinline
func (c Cat) Quack() {
println(c.Name + " meow")
}
func main() {
var c Duck = Cat{Name: "draven"}
c.Quack()
}
如果我们在初始化变量时使用指针类型 &Cat{Name: “draven”} 也能够通过编译,不过生成的汇编代码和上一节中的几乎完全相同.
初始化结构体: 在栈上初始化结构体数据, 并且栈上生成一个指向结构体的指针;
类型转换: 将指向结构体的指针和itab传入convT2I函数, 申请一块堆内存, 并将结构体数据拷贝至内存, 并返回一个iface置于栈上.
使用结构体实现接口带来的开销会大于使用指针实现, 而动态派发在结构体上的表现非常差, 这也提醒我们应当尽量避免使用结构体类型实现接口;使用结构体带来的巨大性能差异不只是接口带来的问题, 带来性能问题主要是因为go语言在函数调用时是传值的, 动态派发的过程只是放大了参数拷贝带来的影响.
反射
go语言反射的三大法则:从interface{}变量可以反射出反射对象;从反射对象可以获取interface{}变量;要修改反射对象,其值必须可设置;
reflect.TypeOf的实现原理并不复杂,它只是将一个interface{}变量转换成了内部的reflect.emptyInterface表示, 然后从中获取相应的类型信息.
用于获取接口值reflcet.Value的函数reflect.ValueOf实现也非常简单,在该函数中我们先调用reflcet.escapes保证当前值逃逸到堆上,然后通过reflect.unpackEface从接口中获取reflect.Value结构体.reflcet.unpackEface会将传入的接口转换成reflect.emptyInterface,然后将具体类型和指针包装成reflect.Value结构体后返回.
reflect包还提供了reflect.rtype.Implements方法可以用于判断某些类型是否遵循特定的接口.
type CustomError struct{}
func (*CustomError) Error() string {
return ""
}
func main() {
typeOfError := reflect.TypeOf((*error)(nil)).Elem()
customErrorPtr := reflect.TypeOf(&CustomError{})
customError := reflect.TypeOf(CustomError{})
fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true
fmt.Println(customError.Implements(typeOfError)) // #=> false
}
找我内推!
【百度】校招&社招&暑期实习内推,有意者评论区留言“内推”,可私聊沟通!咨询秒回!
工作地区:
北京、上海、广州、深圳、其他
招聘岗位:
技术类: C++/Golang/PHP研发工程师、机器学习/数据挖掘/自然语言处理工程师、移动软件研发工程师、web前端研发工程师等
产品类: 产品经理、产品运营、商业产品经理、设计等方向
职能支持类: 销售营销、运营分析、战略投资、财务等
内推岗位达839个职位,持续更新