反射
环境
- golang版本:golang-1.17.8
- 系统Mac-m1-pro
参考
GOT
- 学习反射的基本使用
- 反射实战案例操作
- 学习反射完成状态初始化工具方法
- 了解反射的一些常见误区。
三大法则
- Reflection goes from interface value to reflection object
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
反射将接口变量转换为反射对象
Reflection goes from interface value to reflection object
接口变量指定是:reflect.TypeOf,reflect.ValueOf的参数i interface{}
,它可以隐式的将接受的类型转换为接口类型
反射对象:reflect.TypeOf,reflectValueOf 返回的反射对象。
- 实操
func main() {
var n int64 = 10
rt := reflect.TypeOf(n)
rv := reflect.ValueOf(n)
fmt.Printf("%T\n", rt) // *reflect.rtype
fmt.Printf("%T\n", rv) // reflect.Value
}
反射将反射对象转换为接口变量
Reflection goes from reflection object to interface value.
完成转换的只能是reflect.ValueOf
反射对象。
- 实操
func main() {
var n int64 = 10
rv := reflect.ValueOf(n)
i := rv.Interface() // 从反射对象转换为 接口变量
fmt.Printf("%T\n", rv) // reflect.Value
fmt.Printf("%T, %v", i, i) // int64, 10
}
修改反射对象,则值必须是可修改的
To modify a reflection object, the value must be settable.
满足可修改的条件:
- 创建反射对象时传入指针变量。
- 修改时候调用Elem获取要修改指针指向的变量
可以通过下面这个例子来理解,执行Elem的操作,获取i变量所在地址并使用*v
来修改变量i的数据。
func main() {
i := 1
v := &i
*v = 10
}
- 实操
func main() {
var n int64 = 10
rv := reflect.ValueOf(&n)
fmt.Println(rv.Elem().CanSet()) // true
rv.Elem().SetInt(999) // 设置要修改的值
fmt.Println("修改后的结果", n) // // 修改后的结果 999
}
实操
案例1 结构体操作
// 动态调用函数
func dynamicCallFunc() {
fn := reflect.ValueOf(sum)
argsList := []reflect.Value{
reflect.ValueOf(1), reflect.ValueOf(2),
}
callResList := fn.Call(argsList)
fmt.Println("调用结果sum", callResList[0].Interface())
}
func sum(n1, n2 int) int {
return n1 + n2
}
type User struct {
Name string `json:"name" form:"name"`
}
func (this *User) SetName(name string) {
this.Name = name
}
func (this *User) GetName() string {
return this.Name
}
// 动态调用方法
func dynamicCallMethod() {
u := &User{Name: "a1"}
// 得到该方法的入参
vMethod := reflect.ValueOf(u).MethodByName("SetName")
// In,out,获取方法的的入参和出参类型
tMethod, _ := reflect.TypeOf(u).MethodByName("SetName")
for i := 1; i < tMethod.Type.NumIn(); i++ { // 0-是方法的调用者
fmt.Println("方法入参类型", tMethod.Type.In(i))
}
fmt.Println("调用方法的入参个数", vMethod.Type().NumIn())
fmt.Println("调用方法的出餐个数", vMethod.Type().NumOut())
valueList := []reflect.Value{reflect.ValueOf("b111")}
callResList := vMethod.Call(valueList)
fmt.Println(len(callResList))
fmt.Println("调用后结果值", u)
}
// 修改结构体字段信息
func modifyFieldVal() {
user := User{Name: "a1"}
uValue := reflect.ValueOf(&user)
uValue.Elem().Field(0).Set(reflect.ValueOf("b1"))
fmt.Printf("%+v", user)
}
// 结构体字段变量遍历
func traverse() {
user := User{Name: "a1"}
uType := reflect.TypeOf(user)
uValue := reflect.ValueOf(user)
// 得到结构体的字段
for i := 0; i < uType.NumField(); i++ {
fmt.Println(uType.Field(i)) // StructField
fmt.Println(uValue.Field(i).Interface()) // 得到结构体中字段的值
}
}
工具方法
通过反射结构上定义的tag,完成变量的初始化
/*
type sexType struct {
Boy int `v:"1" d:"男孩"`
Girl int `v:"2" d:"女孩"`
}
var (
SexType = &sexType{}
SexTypeMap = InitState(SexType)
)
*/
// InitState 根据tag初始化,结构体
func InitState(obj interface{}) map[int]string {
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
panic("obj must is prt")
}
if reflect.TypeOf(obj).Elem().Kind() != reflect.Struct {
panic("obj must is struct")
}
objType := reflect.TypeOf(obj).Elem()
objValue := reflect.ValueOf(obj).Elem()
var statusMap = make(map[int]string)
for i := 0; i < objType.NumField(); i++ {
statusStr := objType.Field(i).Tag.Get("v")
desc := objType.Field(i).Tag.Get("d")
if desc == "" {
panic("every filed should have status and desc")
}
status, _ := strconv.Atoi(statusStr)
statusMap[status] = desc
objValue.Field(i).SetInt(int64(status))
}
return statusMap
}
Q&A
reflect.TypeOf/ValueOf ,应该是传地址还是非地址呢?
-
看你具体的操作是什么,
-
如果是Elem的则必须要求是指定Kind类型,或者需要修改信息反射对象信息。
reflect.TypeOf().Elem,【Array, Chan, Map, Ptr, or Slice.】
reflect.ValueOf().Elem,【Interface , Ptr.】
- 如果只是获取反射信息,则参数可以不用地址。
Kind 和 Elem的区别?
-
Kind:只是用来获取反射对象的,底层数据类型,
-
Elem:
reflect.TypeOf,返回反射对象的元素类型,
reflect.ValueOf,返回接口或者指针指向的对象。
修改反射对象信息为什么要Elem?
是否允许修改反射对象值,可以通过CanSet来确定,而Elem的操作就有设置flag的。并标记 flagAddr,
然后通过 SetXXXX()操作,修改 指针变量指向的地址数据,
// 判断是否等于 flagAddr
func (v Value) CanSet() bool {
return v.flag&(flagAddr|flagRO) == flagAddr
}
func main() {
var n int64 =1
reflect.ValueOf(&n).Elem().SetInt(999)// n = 999
}
func (v Value) SetInt(x int64) {
v.mustBeAssignable()
switch k := v.kind(); k {
default:
panic(&ValueError{"reflect.Value.SetInt", v.kind()})
case Int:
*(*int)(v.ptr) = int(x) // 获取指针变量指向的数据地址,并修改数据
}
}
通过Elem的操作,就比如下面的 *v操作,获取指针变量v指向的i的地址,并修改i的数据为10,
func main() {
i := 1
v := &i
*v = 10
}