反射

48 阅读4分钟

反射

环境

  • 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
}