前言
go interface使得go语言可像java语言一样具有多态,reflect是go用于将interface进行反射处理的package,本文尝试以初学者的角度详细解释go的interface核reflect。
interface
下面一段代码是interface的一个简单示例,一个IPrint接口,具有Print()方法。其中结构体PrintA和PrintB都实现了IPrint接口。一个Print(p IPrint)方法传入一个IPrint接口,并调用接口的Print()方法。在主程序调用中,一个IPrint的变量p依次赋值为PrintA类型和PrintB类型,并实现了同一个函数不同的实现调用,这就是多态。
package main
import "fmt"
type IPrint接口,具有 interface {
Print()
}
type PrintA struct {
}
func (p PrintA) Print() {
fmt.Println("A")
}
type PrintB struct {
}
func (p PrintB) Print() {
fmt.Println("B")
}
func Print(p IPrint) {
p.Print()
}
func main() {
var p IPrint
p = PrintA{}
Print(p)
p = PrintB{}
Print(p)
}
程序输出如下:
A
B
在go语言中还有一个0方法的接口 interface{},由于所有类型都实现了至少0个方法,因此所有类型都可以赋值给空接口变量。
var i interface{}
i = 1
i = PrintA{}
i = PrintB{}
interface实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,下面分别是空接口和带方法的接口是使用的数据结构:
//Eface是interface{}底层使用的数据结构。数据域中包含了一个void*指针,和一个类型结构体的指针。
struct Eface
{
Type* type;
void* data;
};
struct Type
{
uintptr size;
uint32 hash;
uint8 _unused;
uint8 align;
uint8 fieldAlign;
uint8 kind; // kind是一个枚举值,每种类型对应了一个编号
Alg *alg;
void *gc;
String *string;
UncommonType *x; UncommonType是指向一个函数指针的数组,收集了这个类型的实现的所有方法。
Type *ptrto;
};
//Iface 是带方法的 interface 底层使用的数据结构
struct Iface
{
Itab* tab;
void* data;
};
struct Itab
{
InterfaceType* inter;
Type* type;
Itab* link;
int32 bad;
int32 unused;
void (*fun[])(void);
};
可以使用类型断言将接口强制转换为具体的类型,如下代码所示:
func findType(p IPrint) {
switch x := p.(type) {
case PrintA:
fmt.Println("PrintA")
case PrintB:
fmt.Println("PrintB")
default:
fmt.Println(x, "not type matched")
}
}
在一般开发中,由于开发者知道该接口有哪些具体类型,因此使用类型断言来的简单些,但如果不知道类型有哪些类型,类型断言将失效,需要借助reflect包具体的反射功能。
reflect
所谓反射,就是指在程序run-time获取interface{}变量的具体类型和具体值。reflect包提供了reflect.TypeOf()和reflect.ValueOf()也获取具体类型和具体值。其中具体类型和具体值分别使用reflect.Type和reflect.Value数据结构表示。示例程序如下:
package main
import(
"fmt"
"reflect"
)
type order struct{
ordId int
customerId int
}
func query(q interface{}) {
t := reflect.TypeOf(q)
// 具体类型的每个字段
for i :=0; i<t.NumField(); i++{
fmt.Println(fmt.Sprintf("field %s, type %s", t.Field(i).Name, t.Field(i).Type))
}
// 具体每个字段的值
v := reflect.ValueOf(q)
for i := 0;i<v.NumField();i++{
fmt.Println(fmt.Sprintf("type %s, value %d", v.Field(i).Kind(), v.Field(i).Int()))
}
}
func main(){
o := order{
ordId: 456,
customerId: 56,
}
query(o)
}
程序输出如下:
field ordId, type int
field customerId, type int
type int, value 456
type int, value 56
其中 reflect.Type 表示具体的类型,reflect.Value表示具体的值,底层源码如下:
// Type is the representation of a Go type.
type Type interface {
// 变量的内存对齐,返回 rtype.align
Align() int
// struct 字段的内存对齐,返回 rtype.fieldAlign
FieldAlign() int
// 根据传入的 i,返回方法实例,表示类型的第 i 个方法
Method(int) Method
// 根据名字返回方法实例,这个比较常用
MethodByName(string) (Method, bool)
// 返回类型方法集中可导出的方法的数量
NumMethod() int
// 只返回类型名,不含包名
Name() string
// 返回导入路径,即 import 路径
PkgPath() string
// 返回 rtype.size 即类型大小,单位是字节数
Size() uintptr
// 返回类型名字,实际就是 PkgPath() + Name()
String() string
// 返回 rtype.kind,描述一种基础类型
Kind() Kind
// 检查当前类型有没有实现接口 u
Implements(u Type) bool
// 检查当前类型能不能赋值给接口 u
AssignableTo(u Type) bool
// 检查当前类型能不能转换成接口 u 类型
ConvertibleTo(u Type) bool
// 检查当前类型能不能做比较运算,其实就是看这个类型底层有没有绑定 typeAlg 的 equal 方法。
// 打住!不要去搜 typeAlg 是什么,不然你会陷进去的!先把本文看完。
Comparable() bool
// 返回类型的位大小,但不是所有类型都能调这个方法,不能调的会 panic
Bits() int
// 返回 channel 类型的方向,如果不是 channel,会 panic
ChanDir() ChanDir
// 返回函数类型的最后一个参数是不是可变数量的,"..." 就这样的,同样,如果不是函数类型,会 panic
IsVariadic() bool
// 返回所包含元素的类型,只有 Array, Chan, Map, Ptr, Slice 这些才能调,其他类型会 panic。
// 这不是废话吗。。其他类型也没有包含元素一说。
Elem() Type
// 返回 struct 类型的第 i 个字段,不是 struct 会 panic,i 越界也会 panic
Field(i int) StructField
// 跟上边一样,不过是嵌套调用的,比如 [1, 2] 就是说返回当前 struct 的第1个struct 的第2个字段,适用于 struct 本身嵌套的类型
FieldByIndex(index []int) StructField
// 按名字找 struct 字段,第二个返回值 ok 表示有没有
FieldByName(name string) (StructField, bool)
// 按函数名找 struct 字段,因为 struct 里也可能有类型是 func 的嘛
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 返回函数第 i 个参数的类型,不是 func 会 panic
In(i int) Type
// 返回 map 的 key 的类型,不是 map 会 panic
Key() Type
// 返回 array 的长度,不是 array 会 panic
Len() int
// 返回 struct 字段数量,不是 struct 会 panic
NumField() int
// 返回函数的参数数量,不是 func 会 panic
NumIn() int
// 返回函数的返回值数量,不是 func 会 panic
NumOut() int
// 返回函数第 i 个返回值的类型,不是 func 会 panic
Out(i int) Type
}
// Value is the reflection interface to a Go value.
type Value struct {
// 反射出来此值的类型,这 typ 是未导出的,从外部调不到 Type 接口的方法
// rtype是上面Type的一个具体实现的结构体
typ *rtype
// 数据形式的指针值
ptr unsafe.Pointer
// 保存元数据
flag
}
一个带修改值的反射示例:
package main
import(
"fmt"
"reflect"
)
type T struct {
A int
B string
}
func main(){
t := T{23,"hello world"}
s := reflect.ValueOf(&t).Elem()
s.Field(0).SetInt(22)
s.Field(1).SetString("XXOO")
typeOfT := s.Type()
for i:=0; i<s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,typeOfT.Field(i).Name, f.Type(), f.Interface())
}
}
进阶
看开源的 json.Unmarshal(data []byte, v interface{}) 方法,可以对reflect的用法更加熟悉。
参考
Go Data Structures: Interfaces
interface
golang中的reflect(反射)
Go Reflect 高级实践
反射 - reflect.ValueOf()