一篇带你全面掌握go reflec 反射的用法

网友投稿 1154 2022-10-11

一篇带你全面掌握go reflec 反射的用法

一篇带你全面掌握go reflec 反射的用法

你为什么要用反射?这个问题请读者自己回答。我强调一下反射的2个弊端:

代码不易阅读,不易维护,容易发生线上panic性能很差,比正常代码慢一到两个数量级

go语言反射里最重要的两个概念是Type和Value,Type用于获取类型相关的信息(比如Slice的长度,struct的成员,函数的参数个数),Value用于获取和修改原始数据的值(比如修改slice和map中的元素,修改struct的成员变量)。它们和go原始数据类型(比如int、float、map、struct等)的转换方式如下图:

上图中的这些方法在下文都会讲到。接下来我们就先讲Type,后讲Value。

reflect.Type

如何得到Type

通过TypeOf()得到Type类型

typeI := reflect.TypeOf(1) typeS := reflect.TypeOf("hello") fmt.Println(typeI) //intfmt.Println(typeS) //stringtypeUser := reflect.TypeOf(&common.User{}) fmt.Println(typeUser) //*common.Userfmt.Println(typeUser.Kind()) //ptrfmt.Println(typeUser.Elem().Kind()) //struct

指针Type转为非指针Type

typeUser := reflect.TypeOf(&common.User{}) typeUser2 := reflect.TypeOf(common.User{})assert.IsEqual(typeUser.Elem(), typeUser2)

获取struct成员变量的信息

typeUser := reflect.TypeOf(common.User{}) //需要用struct的Type,不能用指针的TypefieldNum := typeUser.NumField() //成员变量的个数for i := 0; i < fieldNum; i++ { field := typeUser.Field(i) fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i, field.Name, //变量名称 field.Offset, //相对于结构体首地址的内存偏移量,string类型会占据16个字节 field.Anonymous, //是否为匿名成员 field.Type, //数据类型,reflect.Type类型 field.IsExported(), //包外是否可见(即是否以大写字母开头) field.Tag.Get("json")) //获取成员变量后面``里面定义的tag}fmt.Println()//可以通过FieldByName获取Fieldif nameField, ok := typeUser.FieldByName("Name"); ok { fmt.Printf("Name is exported %t\n", nameField.IsExported())}//也可以根据FieldByIndex获取FieldthirdField := typeUser.FieldByIndex([]int{2}) //参数是个slice,因为有struct嵌套的情况fmt.Printf("third field name %s\n", thirdField.Name)

获取struct成员方法的信息

typeUser := reflect.TypeOf(common.User{})methodNum := typeUser.NumMethod() //成员方法的个数。接收者为指针的方法【不】包含在内for i := 0; i < methodNum; i++ { method := typeUser.Method(i) fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())}fmt.Println()typeUser2 := reflect.TypeOf(&common.User{})methodNum = typeUser2.NumMethod() //成员方法的个数。接收者为指针或值的方法【都】包含在内,也就是说值实现的方法指针也实现了(反之不成立)for i := 0; i < methodNum; i++ { method := typeUser2.Method(i) fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())}

获取函数的信息

func Add(a, b int) int { return a + b}typeFunc := reflect.TypeOf(Add) //获取函数类型fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)argInNum := typeFunc.NumIn() //输入参数的个数argOutNum := typeFunc.NumOut() //输出参数的个数for i := 0; i < argInNum; i++ { argTyp := typeFunc.In(i) fmt.Printf("第%d个输入参数的类型%s\n", i, argTyp)}for i := 0; i < argOutNum; i++ { argTyp := typeFunc.Out(i) fmt.Printf("第%d个输出参数的类型%s\n", i, argTyp)}

判断类型是否实现了某接口

//通过reflect.TypeOf((*)(nil)).Elem()获得接口类型。因为People是个接口不能创建实例,所以把nil强制转为*common.People类型typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)t1 := reflect.TypeOf(common.User{})t2 := reflect.TypeOf(&common.User{})//如果值类型实现了接口,则指针类型也实现了接口;反之不成立fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))

reflect.Value

如果获得Value

通过ValueOf()得到Value

iValue := reflect.ValueOf(1)sValue := reflect.ValueOf("hello")userPtrValue := reflect.ValueOf(&common.User{ Id: 7, Name: "杰克逊", Weight: 65, Height: 1.68,})fmt.Println(iValue) //1fmt.Println(sValue) //hellofmt.Println(userPtrValue) //&{7 杰克逊 65 1.68}

Value转为Type

iType := iValue.Type()sType := sValue.Type()userType := userPtrValue.Type()//在Type和相应Value上调用Kind()结果一样的fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind()) fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind()) fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind())

指针Value和非指针Value互相转换

userValue := userPtrValue.Elem() //Elem() 指针Value转为非指针Valuefmt.Println(userValue.Kind(), userPtrValue.Kind()) //struct ptruserPtrValue3 := userValue.Addr() //Addr() 非指针Value转为指针Valuefmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr

得到Value对应的原始数据

通过Interface()函数把Value转为interface{},再从interface{}强制类型转换,转为原始数据类型。或者在Value上直接调用Int()、String()等一步到位。

fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())user := userValue.Interface().(common.User)fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)user2 := userPtrValue.Interface().(*common.User)fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)

空Value的判断

通过Value修改原始数据的值

var i int = 10var s string = "hello"user := common.User{ Id: 7, Name: "杰克逊", Weight: 65.5, Height: 1.68,}valueI := reflect.ValueOf(&i) //由于go语言所有函数传的都是值,所以要想修改原来的值就需要传指针valueS := reflect.ValueOf(&s)valueUser := reflect.ValueOf(&user)valueI.Elem().SetInt(8) //由于valueI对应的原始对象是指针,通过Elem()返回指针指向的对象valueS.Elem().SetString("golang")valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通过Name返回类的成员变量

强调一下,要想修改原始数据的值,给ValueOf传的必须是指针,而指针Value不能调用Set和FieldByName方法,所以得先通过Elem()转为非指针Value。

未导出成员的值不能通过反射进行修改。

addrValue := valueUser.Elem().FieldByName("addr")if addrValue.CanSet() { addrValue.SetString("北京")} else { fmt.Println("addr是未导出成员,不可Set") //以小写字母开头的成员相当于是私有成员}

通过Value修改Slice

users := make([]*common.User, 1, 5) //len=1,cap=5users[0] = &common.User{ Id: 7, Name: "杰克逊", Weight: 65.5, Height: 1.68,}sliceValue := reflect.ValueOf(&users) //准备通过Value修改users,所以传users的地址if sliceValue.Elem().Len() > 0 { //取得slice的长度 sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("令狐一刀") fmt.Printf("1st user name change to %s\n", users[0].Name)}

甚至可以修改slice的cap,新的cap必须位于原始的len到cap之间,即只能把cap改小。

sliceValue.Elem().SetCap(3)

通过把len改大,可以实现向slice中追加元素的功能。

sliceValue.Elem().SetLen(2)//调用reflect.Value的Set()函数修改其底层指向的原始数据sliceValue.Elem().Index(1).Set(reflect.ValueOf(&common.User{ Id: 8, Name: "李达", Weight: 80, Height: 180,}))fmt.Printf("2nd user name %s\n", users[1].Name)

修改map

Value.SetMapIndex()函数:往map里添加一个key-value对

Value.MapIndex()函数: 根据Key取出对应的map

u1 := &common.User{ Id: 7, Name: "杰克逊", Weight: 65.5, Height: 1.68,}u2 := &common.User{ Id: 8, Name: "杰克逊", Weight: 65.5, Height: 1.68,}userMap := make(map[int]*common.User, 5)userMap[u1.Id] = u1mapValue := reflect.ValueOf(&userMap) //准备通过Value修改userMap,所以传userMap的地址mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2)) //SetMapIndex 往map里添加一个key-value对mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的mapfor k, user := range userMap { fmt.Printf("key %d name %s\n", k, user.Name)}

调用函数

valueFunc := reflect.ValueOf(Add) //函数也是一种数据类型typeFunc := reflect.TypeOf(Add)argNum := typeFunc.NumIn() //函数输入参数的个数args := make([]reflect.Value, argNum) //准备函数的输入参数for i := 0; i < argNum; i++ { if typeFunc.In(i).Kind() == reflect.Int { args[i] = reflect.ValueOf(3) //给每一个参数都赋3 }}sumValue := valueFunc.Call(args) //返回[]reflect.Value,因为go语言的函数返回可能是一个列表if typeFunc.Out(0).Kind() == reflect.Int { sum := sumValue[0].Interface().(int) //从Value转为原始数据类型 fmt.Printf("sum=%d\n", sum)}

调用成员方法

common.User{ Id: 7, Name: "杰克逊", Weight: 65.5, Height: 1.68,}valueUser := reflect.ValueOf(&user) //必须传指针,因为BMI()在定义的时候它是指针的方法bmiMethod := valueUser.MethodByName("BMI") //MethodByName()通过Name返回类的成员变量resultValue := bmiMethod.Call([]reflect.Value{}) //无参数时传一个空的切片result := resultValue[0].Interface().(float32)fmt.Printf("bmi=%.2f\n", result)//Think()在定义的时候用的不是指针,valueUser可以用指针也可以不用指针thinkMethod := valueUser.MethodByName("Think")thinkMethod.Call([]reflect.Value{})valueUser2 := reflect.ValueOf(user)thinkMethod = valueUser2.MethodByName("Think")thinkMethod.Call([]reflect.Value{})

创建对象

创建struct

user :=t := reflect.TypeOf(common.User{})value := reflect.New(t) //根据reflect.Type创建一个对象,得到该对象的指针,再根据指针提到reflect.Valuevalue.Elem().FieldByName("Id").SetInt(10)user := value.Interface().(*common.User) //把反射类型转成go原始数据类型Call([]reflect.Value{})

创建slice

var slice []common.UsersliceType := reflect.TypeOf(slice)sliceValue := reflect.MakeSlice(sliceType, 1, 3)sliceValue.Index(0).Set(reflect.ValueOf(common.User{ Id: 8, Name: "李达", Weight: 80, Height: 180,}))users := sliceValue.Interface().([]common.User)fmt.Printf("1st user name %s\n", users[0].Name)

创建map

var userMap map[int]*common.UsermapType := reflect.TypeOf(userMap)// mapValue:=reflect.MakeMap(mapType)mapValue := reflect.MakeMapWithSize(mapType, 10)user := &common.User{ Id: 7, Name: "杰克逊", Weight: 65.5, Height: 1.68,}key := reflect.ValueOf(user.Id)mapValue.SetMapIndex(key, reflect.ValueOf(user)) //SetMapIndex 往map里添加一个key-value对mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的mapuserMap = mapValue.Interface().(map[int]*common.User)fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)

reflect包里除了MakeSlice()和MakeMap(),还有MakeChan()和MakeFunc()。

没有无缘无故的荣耀

赞助我写出更好的博客

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:[Mitoo-weapp]是一款基于小程序的ui组件库
下一篇:一个简单的微信小程序,颜值检测仪(一个简单的微信小程序,颜值检测仪怎么设置)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~