Go反射
反射
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect包访问程序的反射信息。
注意:Go中反射不能获取和修改 私有的属性以及方法
优缺点
-
优点
- 反射可以增加程序的灵活性,避免将程序写死到代码里
- 代码简洁,提高代码的复用率,外部调用方便
- 对于任意一个类(or go的结构体),都能够知道这个类(or go的结构体)的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
-
缺点
- 使用反射比正常代码性能低。可以进行优化。
- 使用反射会模糊程序内部逻辑,使代码更复杂。
- 会破坏私密性。不过在Go中反射不能获取和修改 私有的属性以及方法。
reflect包
Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value。
任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value(对象的信息) 和 Type(对象的类型的信息)。
reflect中Type与Kind区别:Kind用于表示某一类(比如结构体类struct,指针类ptr),Type用于表示某一类中的具体类型(A{},*A)
基于实践学习
-
获取结构体标签
- 通过reflect.TypeOf()获取type,然后再通过type的Field相关方法获取结构体对应的字段信息structField,然后再通过structField的Tag方法获取该字段对应的tag,然后再调用tag的Get方法获取指定的标签
-
type A struct { Name string `thy:"mobile;max:11;min:6"` // 一般通过" "来区分不同类的tag,通过";"来区别同类tag的不同标志 } aIns := &A{Name: "test"} reflect.TypeOf(aIns).FieldByName("Name").Tag.Get("thy") // mobile;max:11;min:6 // 后续通过;区分开mobile,max:11,min:6,然后根据不同的标志调用不同方法去验证值 - 代码示例:github.com/tianheyi/go…
-
通过类型动态创建实例
- 使用
reflect.New(typ Type)或者reflect.NewAt(typ Type, p unsafe.Pointer)方法创建对应类型的实例,会返回对应类型实例(为reflect.Value类型) - 如果需要将
reflect.Value类型的实例转换成对应类型的实例,可通过reflect.Value对象调用Interface()方法将其转化为interface{}类型,然后通过类型断言来转化为实际类型的实例。
- 使用
-
通过函数名调用方法
-
调用普通方法
func Add(x, y int) int { return x + y } func main() { funcV := reflect.ValueOf(Add) // 将Add方法转化为reflect.Value类型 result := funcV.Call([]reflect.Value{reflect.ValueOf(3), reflect.ValueOf(2)}) // 调用该方法,并且传入两个参数3和2,对应形参中的x,y fmt.Println(result[0].Int()) // 获取第一个返回值,以int类型打印 } -
调用某对象方法
type A struct { Name string } func (a A) SetName1(name string) { a.Name = name } func (a *A) SetName2(name string) { a.Name = name } func (a A) GetName1() string { return a.Name } func (a *A) GetName2() string { return a.Name } func main() { // 直接调用 aIns := &A{Name: "thy"} aIns.SetName1("thy1") fmt.Println(aIns.GetName1(),aIns.GetName2()) aIns.SetName2("thy1") fmt.Println(aIns.GetName1(),aIns.GetName2()) // 通过反射包调用 aIns = &A{Name: "thy"} v := reflect.ValueOf(aIns) fmt.Println(v.Kind(), v.Elem().Kind()) v.Elem().MethodByName("SetName1").Call([]reflect.Value{reflect.ValueOf("thy1")}) result1 := v.MethodByName("GetName1").Call([]reflect.Value{}) result2 := v.MethodByName("GetName2").Call([]reflect.Value{}) fmt.Println(result1[0].String(), result2[0].String()) v.MethodByName("SetName2").Call([]reflect.Value{reflect.ValueOf("thy1")}) result1 = v.MethodByName("GetName1").Call([]reflect.Value{}) result2 = v.MethodByName("GetName2").Call([]reflect.Value{}) fmt.Println(result1[0].String(), result2[0].String()) }打印结果:
thy thy thy1 thy1 ptr struct thy thy thy1 thy1通过打印结果可知,无论是直接调用还是通过反射包调用,方法SetName1都没有修改变量成功。
方法SetName1修改变量并没有成功,而SetName2修改变量成功。并且在
-
直接调用方法时,用指针类型(pointer)与非指针类型(value)实例调用方法(无论是修改实例的方法还是与修改实例无关的方法)时不受方法集的约束,因为编译器总是去查找全部方法,并且自动转换调用者类型。比如:
aIns := &A{Name: "thy"} aIns.SetName1("thy1") // 编译器会自动转换成(*aIns).SetName1("thy1") -
反射包调用方法时,当调用修改实例的方法(SetName1、SetName2)时,只有非指针类型(A)才能调用非指针方法(SetName1),指针类型(*A)才能调用指针方法(SetName2),不然会panic。而调用非修改实例的方法(GetName1、GetName2)时,非指针类型和指针类型都可以调用而不会panic。所以可以看出,当使用反射调用方法时,不全是查找全部方法,比如当调用修改实例的方法时。
-
-