Go语言学习之反射

简介

Go反射提供了一种可以在运行时(runtime)操作任意类型对象的能力。

在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型(Type),一个是具体类型对应的值(Value)
更精确的描述是,value指的是实现接口的底层具体数据,type指的是数据的完整类型

1
2
3
4
5
获取反射类型
reflect.TypeOf()
获取反射值
reflect.ValueOf()

Type

Type是对go类型的描述,其方法分为两类,一类是所有类型通用的方法,一类是类型特有方法

1
2
3
type Type interface{
...
}

通用方法

1
2
3
4
5
6
7
8
9
10
11
12
Align() int 返回内存中分配时类型对齐长度
FieldAlign() int 返回当该类型用于结构体字段时的内存对齐长度
Method(int) Method 返回类型结果集中的第i个方法
MethodByName(string)(Method,bool) 返回结果集中指定名称的方法以及一个bool
NumMethod() int 返回类型结果集中导出方法的数量
Size() uintptr 返回存储该类型需要的字节数,类似于unsafe.Sizeof
Kind() Kind 返回该类型的种类
String() string 返回该类型的字符串表述
Implements(u Type) bool 判断类型是否实现了接口类型u
AssignableTo(u Type) bool 判断类型是否可分配给类型u
ConvertibleTo(u Type) bool 判断类型是否可转换为类型u
Comparable() bool 报告当前类型的值是否可比较

kind表示类型种类,比如Bool、Int、Int8等,对应值0-26的常量
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)

类型特有方法(kind-specific methods)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Methods applicable only to some types, depending on Kind.
// The methods allowed for each kind are:
//
// Int*, Uint*, Float*, Complex*: Bits
// Array: Elem, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic.
// Map: Key, Elem
// Ptr: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
Elem() Type 返回类型元素类型
ChanDir() ChanDir 返回channel类型的方向
IsVariadic() bool 返回Func最后入参是否是...
In(i int) Type 返回第i个入参
Out(i int) Type 返回第i个出参
Key() Type 返回map的键类型

Value

Value是一个Go值的反射接口

1
2
3
4
5
type Value struct{
typ *rtype
ptr unsafe.Pointer
flag
}

Value类型的方法也分为通用方法和类型特定方法两种

1
func (v Value) Elem() Value 返回interface v包含的值或指针v指向的值

应用

Go反射中把任意一个对象分为reflect.Value和reflect.Type,值得注意的一点是reflect.Value同时持有一个对象的reflect.Value和reflect.Type

reflect.Value与具体类型的转换

1
2
3
4
u:=User{"张三",20}
v:=reflect.Value(u) //u转换为reflect.Value
u2:=v.Interface().(User) //reflect.Value还可以转回原始类型User
fmt.Println(u2)

通过reflect.Value获取reflect.type

1
2
t:=v.Type()
fmt.Println(t)

获取类型种类

1
2
fmt.Println(t.Kind())
fmt.Println(v.Kind())

遍历字段和方法

通过反射,我们可以获取一个结构体类型字段,也可以获取一个类型的导出方法,这样我们可以在运行时了解一个类型的结构

1
2
3
4
5
6
7
for i:=0;i<t.NumField();i++{
fmt.Println(t.Field(i).Name)
}
for i:=0;i<t.NumMethod();i++{
fmt.Println(t.Method(i).Name)
}

修改字段的值

动态修改某个字段的值有两种办法,一种是直接通过导出的字段或方法修改,另一种是使用反射

1
2
3
4
x:=2
v:=reflect.ValueOf(&x)
v.Elem().SetInt(100)
fmt.Print(x)

reflect.ValueOf函数返回的是一份值的拷贝,所以我们需要传入变量地址来修改原始变量。另外需要调用Elem方法找到这个指针指向的值,最后使用SetInt方法修改值

动态调用方法

结构体方法还可以使用反射进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
u:=User{"张三",20}
v:=reflect.ValueOf(u)
mPrint:=v.MethodByName("Print")
args:=[]reflect.Value{reflect.ValueOf("前缀")}
fmt.Println(mPrint.Call(args))
tyep User struct{
Name string
Age int
}
func (u User)Print(prefix string){
fmt.Printf("%s:Name is %s,Age is %d",prefix,u.Name,u.Age)
}

MethodByName方法可以让我们根据一个方法名获取一个方法对象,然后我们构建好参数,最后通过Call达到动态调用方法的目的(Call方法参数是一个reflect.Value类型的切片)
获取到的方法我们可以使用IsValid来判断是否可用

获取字段Tag

Tag是标记到字段上的,我们要先获取字段,再通过字段获取Tag

1
2
3
4
5
6
7
8
9
10
11
type User struct{
Name string `json:"name" bson:"b_name"`
Age int `json:"age" bson:"b_age"`
}
var u User
t:=reflect.TypeOf(u)
for i:=0;i<T.NumField();i++{
field:=t.Field(i)
fmt.Println(field.Tag.Get("json"))
}

参考资料

laws of reflection