反射的基本介绍
- 反射可以在运行时动态获取变量的信息,如变量的类型(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是可以相互转换的
我们来看一个相互转换的例子,这在反射中是很常用的
var student Student
var num ntfunc 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()
func main() {var num int = 10// 这里需要传地址rVal = reflect.ValueOf(&num)// 这里不能直接set,需要用指针指向的elem来修改rVal.Elem().SetInt(20)
}
反射的几种最佳实践
1. 使用反射遍历结构体字段,调用结构体方法,并获取结构体标签
package main
import("fmt""reflect"
)
type Monster struct {Name string `json:name`Age int `json:monster_age`Score float32Sex 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 = Names.Age = Ages.Score = Scores.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.Valueparams append(params, reflect.Valueof(10))params append(params, reflect.Valueof(40))// Call方法参数类型reflect.Value类型的切片类型,因此上面先构造切片,返回值也是一个切片res := rval.Method(0).Call(params)//传入的参数是[]reflect.Valuefmt.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”)
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.ValueinValue []reflect.Valuen 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、使用反射操作任意结构体
type user struct {Userld stringName string
}
func TestReflectStruct(t *testing.T) {var ( model *usersv 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、使用反射创建并操作结构体
package test
import ("testing""reflect"
)
type user struct {Userld stringName string
}
func TestReflectStructPtr(t *testing.T) {var(model *userst reflect.Typeelem reflect.Value)st = reflect.Typeof(model)/获取类型*usert.Log("reflect.Typeof",st.Kind().String())//ptrst = st.Elem()/st指向的类型t.Log("reflect.TypeOf.Elem",st.Kind).String())//structelem = reflect.New(st)/New返回一个Value类型值,该值持有一个指向类型为type的新申请的零值的指针t.Log("reflect.New",elem.Kind().String())//ptrt.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)