反射的基本介绍
- 反射可以在运行时动态获取变量的信息,如变量的类型(type),类别(kind)。
- 如果是结构体变量,还可以获取到变量的字段、方法等结构体本身信息;
- 通过反射,可以修改变量的值或调用关联的方法;
- 使用反射需要import("reflect")
我们前面的文章空接口接收任意类型的变量,通过typeof来判断变量类型
反射的基础应用场景
- 不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这时需要对函数或方法进行反射,如下例传入函数指针,及回调函数的参数
func bradge(funcPtr interface{}, args ...interface{})
在bradge中通过反射来执行传入的函数 - 结构体序列化时,指定了字段tag,通过反射生成对应的字符串
反射的重要函数
- reflect.TypeOf(变量名)--获取变量的类型,返回值是reflect.Type类型(是一个接口类型)
- reflect.ValueOf(变量名)--获取变量的值,返回reflect.Value类型(是一个struct),具体可查go的反射包的介绍
变量、interface{}、reflect.Value是可以相互转换的
我们来看一个相互转换的例子,这在反射中是很常用的
go
var student Student
var num nt
func trans(b interface{}) {
// 将b这个interface{}类型转为reflect.Value类型
rVal := reflect.ValueOf(b)
// 将reflect.Value类型转为interface{}类型
iVal := rVal.Interface()
// 将interface类型转为原来的变量类型--类型断言
val := iVal.(Student)
// 若b是int类型,还可以直接用reflect.Value类型里面的Int方法取变量值,之后才能和其他int类型进行操作
n := rVal.Int()
}
- reflect.Value.kind和reflect.Value.type的区别
kind是变量的分类,是大的范畴
kind的取值是枚举里面的值,是一个常量,type是变量的具体类型,type的取值可以是自定义类型,比如一个自定义结构体类型Student,它的kind是struct,但type是Student - 在使用反射的方式来获取变量的值并获取对应的数据类型,必须类型一致,比如rVal := reflect.ValueOf(b),假设这里的rVal的值是一个int类型,但我们用rVal.Float()来获取值,那么就会panic。
- 通过反射来修改变量时,使用SetXxx的方法来修改变量值,这时需要传递变量的指针,同时需要用到reflect.Elem().SetXxx()而不是reflect.SetXxx()
go
func main() {
var num int = 10
// 这里需要传地址
rVal = reflect.ValueOf(&num)
// 这里不能直接set,需要用指针指向的elem来修改
rVal.Elem().SetInt(20)
}
反射的几种最佳实践
1. 使用反射遍历结构体字段,调用结构体方法,并获取结构体标签
go
package main
import(
"fmt"
"reflect"
)
type Monster struct {
Name string `json:name`
Age int `json:monster_age`
Score float32
Sex string
}
func (s Monster)Print() {
fmt.Println("print monster", s)
}
func (s Monster)GetSum(n1, n2 int) int {
return n1 + n2
}
func (s Monster) Set(Name string, Age int, Score float32, Sex string) {
s.Name = Name
s.Age = Age
s.Score = Score
s.Sex = Sex
}
func TestStruct(b interface{}) {
rtype := reflect.TypeOf(b)
rVal := reflect.ValueOf(b)
kd := rVal.Kind()
if kd != reflect.Struct {
fmt.Println("expect struct")
return
}
// 获取该结构体有几个字段
num := rVal.NumField()
fmt.Printf("struct has %d filed", num)
for i :=0; i < num; i++ {
// 通过Field方法遍历字段,注意这里Field(i)是reflect.Value类型,不能直接用于运算,也需要进行类型断言才能运算
fmt.Printf("field %d value is %v", i, rVal.Field(i))
// 这里不能用value获取tag,只能用reflect.Type来获取标签,如果结构体不止一个标签,那么这里只能获取json的标签数据
tagval := rtype.Field(i).Tag.Get("json")
if tagval != "" {
fmt.Printf("field %d tag is:%v", i, tagval)
}
}
//获取到该结构体有多少个方法,方法序号从0开始,方法的排序默认按照函数名的ascii码大小比较排序
numOfMethod := val.NumMethod()
fmt.Printf("struct has %d methods\n", numOfMethod)
// var params []reflect.Value
// 调用第二个方法:Print方法
rval.Method(1).Call(nil)
// 调用结构体的第1个方法Method(0)
var params []reflect.Value
params append(params, reflect.Valueof(10))
params append(params, reflect.Valueof(40))
// Call方法参数类型reflect.Value类型的切片类型,因此上面先构造切片,返回值也是一个切片
res := rval.Method(0).Call(params)//传入的参数是[]reflect.Value
fmt.Println("res=",res[0].Int())//返回结果,返回的结果是[]reflect.Value*/
}
func main() {
var m1 Monster := Monster{
Name : "huangshulang",
Age : 400,
Score : 30.9,
}
// 将Monster实例传给TestStruct函数即可调用结构体相关的方法,这在一些固定框架的代码中很有用,我们通常只需要定义好结构体及对应的方法,框架里面通过反射即可相关功能自动处理,无需显式通过结构体实例调用
TestStruct(m1)
}
补充上面Call方法的官方说明:
2、修改结构体、标签
这个基本上与上面一致,需要注意的市,我们在传参的时候,TestStruct(&m1)要传地址,才能修改成功,其余的就是用类似
rval.Elem().Field(0).SetString("白象精")修改方法来操作修改即可
3、适配器实现
定义了两个函数test1和test2,定义一个适配器函数用作统一处理接口
(1)定义了两个函数
test1 := func(v1 int, v2 int){
t.Log(v1,v2)
}
test2 := func(v1 int, v2 int, s string){
t.Log(v1,v2,s)
}
现在要定义一个适配器函数用作统一处理接口,其大致结构如下
bridge:=func(call interface{}, args.....interface{}){
// 内容
}
实现调用test1对应的函数
bridge(test1, 1, 2)
实现调用test2对应的函数
bridge(test2, 1, 2, "test2")
go
func TestReflectFunc(t *testing.T){
call1 := func(v1 int,v2 int){
t.Log(v1,v2)
}
call2 := func(v1 int,v2 int,s string){
t.Log(v1,v2,s)
}
var(
function reflect.Value
inValue []reflect.Value
n int
)
bridge := func(call interface{}, args...interface{}){
n = len(args)
inValue=make(reflect.Value, n)
for i := 0; i<n; i++ {
inValue[i]=reflect.ValueOf(args[i])
}
function reflect.ValueOf(call)
function.Call(inValue)
}
bridge(call1,1,2)
bridge(call2,1,2,"test2")
4、使用反射操作任意结构体
go
type user struct {
Userld string
Name string
}
func TestReflectStruct(t *testing.T) {
var (
model *user
sv reflect.Value
)
// model指向一个user对象
model = &user{}
// 将model转成一个reflect.Value类型
sv = reflect.ValueOf(model)
t.Log("reflect.ValueOf",sv.Kind().String())
// 这里获取Elem之后,sv实际就指向user对象了,后续就能直接用sv来操作结构体了
sv = sv.Elem()
t.Log("reflect.ValueOf.Elem",sv.Kind().String())
sv.FieldByName("Userld").SetString("12345678")
sv.FieldByName("Name").SetString("nickname")
t.Log("model",model)
}
5、使用反射创建并操作结构体
go
package test
import (
"testing"
"reflect"
)
type user struct {
Userld string
Name string
}
func TestReflectStructPtr(t *testing.T) {
var(
model *user
st reflect.Type
elem reflect.Value
)
st = reflect.Typeof(model)/获取类型*user
t.Log("reflect.Typeof",st.Kind().String())//ptr
st = st.Elem()/st指向的类型
t.Log("reflect.TypeOf.Elem",st.Kind).String())//struct
elem = reflect.New(st)/New返回一个Value类型值,该值持有一个指向类型为type的新申请的零值的指针
t.Log("reflect.New",elem.Kind().String())//ptr
t.Log("reflect.New.Elem",elem.Elem().Kind().StringO)//struct
/model就是创建的user结构体变量(实例)
model = elem.Interface().(*user)/model:是user它的指向和elem是一样的
elem = elem.Elem()/取得elem指向的值
elem.FieldByName("Userld").SetString("12345678")/赋值.
elem.FieldByName("Name").SetString("nickname")
t.Log("modelmodel.Name", model ,model.Name)