GO的接口interface和反射
interface定义
接口是一组方法签名(声明的是一组方法的集合)。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口
示例:
1 |
|
空interface{}
interface{} 类型,空接口,没有方法的接口,所有类型都至少实现了 0 个方法,所以 所有类型都实现了空接口
1 | func DoSomething(v interface{}) { |
v是interface类型,其他类型需要转换断言
指针和接口
上面的Cat的speak方法是值接收器,下面的改为指针接收器
1 | func (c *Cat) Speak() string { |
这时访问需要指针类型定义才可以访问,不然会报错
1 | animals := []Animal{new(Dog), new(Cat)} |
这种方式下Dog也可以正常工作,因为一个指针类型可以通过其相关的值类型来访问值类型的方法,但是反过来不行。即,一个 * Dog 类型的值可以使用定义在 Dog 类型上的 Speak() 方法,而 Cat 类型的值不能访问定义在 * Cat 类型上的方法
interface实现
interface实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,eface 和 iface
eface
1 | struct Eface |
eface 表示空的 interface{},它用两个机器字长表示,第一个字 type 是指向实际类型描述的指针,第二个字 data 代表数据指针
iface
1 | struct Iface |
iface 表示至少带有一个函数的 interface, 它也用两个机器字长表示,第一个字 tab 指向一个 itab 结构,第二个字 data 代表数据指针
data
data 用来保存实际变量的地址。
data 中的内容会根据实际情况变化,因为 golang 在函数传参和赋值时是 值传递 的,所以:
1.如果实际类型是一个值,那么 interface 会保存这个值的一份拷贝。interface 会在堆上为这个值分配一块内存,然后 data 指向它。
2.如果实际类型是一个指针,那么 interface 会保存这个指针的一份拷贝。由于 data 的长度恰好能保存这个指针的内容,所以 data 中存储的就是指针的值。它和实际数据指向的是同一个变量。
type
type 类型结构如下,反射方法会涉及到type域
1 | struct Type |
Itab
Iface和Eface略有不同,它是带方法的interface底层使用的数据结构。data域同样是指向原始数据的,而Itab的结构如下
1 | struct Itab |
1.inter 指向对应的 interface 的类型信息。
2.type 和 eface 中的一样,指向的是实际类型的描述信息 type
3.fun 为函数列表,表示对于该特定的实际类型而言,interface中所有函数的地址,一个Iface中的具体类型中实现的方法会被拷贝到Itab的fun数组中
Itab函数生成
假设 interface 有 m 个函数, struct 有 n 个函数,那么 itab 中的函数表生成的时间复杂度为 O(m* n)
遍历 interface 的所有函数,对每次迭代都从 struct 中遍历找到匹配的函数
Type结构体中是有个UncommonType字段的,里面有张方法表,类型所实现的方法都在里面。而在Itab中有个InterfaceType字段,这个字段中也有一张方法表,就是这个接口所要求的方法。
这两处方法表都是排序过的,只需要一遍顺序扫描进行比较,应该可以知道Type中否实现了接口中声明的所有方法,所以实现了 O(m + n) 时间复杂度的算法
interface参数传递与函数调用
1 | type Binary uint64 |
1.分配一块内存 p, 并且将对象 a 的内容拷贝到 p 中
2.创建 iface 对象 i,将 i.tab 赋值为 itab<Stringer, Binary>。将 i.data 赋值为 p
3.使用 i 作为参数调用 test 函数。
4.当 test 函数执行 s.String 时,实际上就是在 s.tab 的 fun 中索引(索引由编译器在编译时生成)到 String 函数,并且调用它
反射
不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射
比如需要一个函数处理各种类型的值:
1 | switch value := value.(type) { |
类型很多,代码会很长,且不好维护
反射可以获取变量的内部信息,reflect包是用来实现运行反射的。通过reflect包能够完成对一个interface{}变量的具体类型以及值的获取
reflect.Type和reflect.Value
1 | reflect.ValueOf() |
获取输入参数接口中的数据的值,如果为空则返回0 <- 注意是0
1 | reflect.TypeOf() |
动态获取输入参数接口中的值的类型,如果为空则返回nil <- 注意是nil
输出了interface{}的真实类型以及真实值
Kind
也是代表类型
1 | type order struct { |
reflect.Type和reflect.Kind
Type代表interface{}实际类型main.order
而Kind代表具体类型struct
NumField() 和Field()
NumField()方法获取一个struct所有的fields,Field(i int)获取指定第i个field的reflect.Value
1 | v := reflect.ValueOf(q) // TypeOf 也可以 |
Int() 和String()
Int()和String()主要用于从reflect.Value提取对应值作为int64和string类型
1 | reflect.ValueOf(a).Int() |
Name()
.Name()可以获取去这个类型的名称
1 | reflect.TypeOf(a).Name() |
NumMethod()和Method()
可以获取struct的方法
1 | t := reflect.TypeOf(s) |
反射创建
1 | type People struct { |
golang的反射性能很差