Go 语言使用其自带的 reflect 包来实现反射。其反射机制就是程序在运行过程中,可以动态地调用对象的方法和属性。
我们知道,一个变量由类型和值两部分组成,类型又包括静态类型和具体类型。静态类型就是在编码时可见的类型(int、string等),而具体类型是在程序运行时系统所见的类型。
interface
interface(接口) 类型是一种特殊的类型,interface 用来表示一组方法集合,所有实现该方法集合的类型都可以被认为是实现了该接口。所以空 interface 类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口。
反射有两种类型 reflect.Value
和 reflect.Type
,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOf
和 reflect.TypeOf
分别获取任意对象的 reflect.Value
和 reflect.Type
。
使用示例:
packagemainimport ( "fmt""reflect") funcmain(){ varxfloat64=3.4t :=reflect.TypeOf(x) fmt.Println("type:", t) v :=reflect.ValueOf(x) fmt.Println("value", v) } //运行结果//type: float64//value 3.4
reflect
- reflect.Value
reflect.ValueOf() 定义如下:
funcValueOf(iinterface{}) Value {...}
ValueOf() 函数用来获取输入参数接口中的数据的值。如果接口为空,则返回 0。
Value 类型为:
typeValuestruct { typ*rtypeptrunsafe.Pointerflag}
示例,修改 struct 结构体字段的值:
funcmain() { p :=person{Name: "微客鸟窝",Age: 18} pv:=reflect.ValueOf(&p) pv.Elem().Field(0).SetString("无尘") fmt.Println(p) } typepersonstruct { NamestringAgeint}
- reflect.Type
reflect.TypeOf() 定义如下:
funcTypeOf(iinterface{}) Type {...}
TypeOf() 函数用来动态获取输入参数接口中的值的类型,如果接口为空,则返回 nil。
reflect.Type 实际上是一个接口,定义了很多方法来获取类型相关的信息:
typeTypeinterface { Implements(uType) bool//方法用于判断是否实现了接口 u;AssignableTo(uType) bool//方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;ConvertibleTo(uType) bool//方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;Comparable() bool//方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。//以下这些方法和Value结构体的功能相同Kind() KindMethod(int) MethodMethodByName(string) (Method, bool) NumMethod() intElem() TypeField(iint) StructFieldFieldByIndex(index []int) StructFieldFieldByName(namestring) (StructField, bool) FieldByNameFunc(matchfunc(string) bool) (StructField, bool) NumField() int}
示例,遍历结构体的字段和方法:
packagemainimport ( "fmt""reflect") funcmain() { p :=person{Name: "微客鸟窝", Age: 18} pt :=reflect.TypeOf(p) //遍历person的字段fori :=0; i<pt.NumField(); i++ { fmt.Println("字段:", pt.Field(i).Name) } //遍历person的方法fori :=0; i<pt.NumMethod(); i++ { fmt.Println("方法:", pt.Method(i).Name) } } typepersonstruct { NamestringAgeint} func (pperson) String() string{ returnfmt.Sprintf("Name is %s,Age is %d",p.Name,p.Age) }
反射3大法则
- 反射可以将 interface 类型变量转换成反射对象
通过 reflect 包的一些函数,可以把接口转换为反射定义的对象。
- reflect.ValueOf() 获取某个变量的值
- reflect.TypeOf() 获取某个变量的静态类型
- reflect.Value.Kind() 获取变量值的底层类型,底层类型可能为int/float/struct/slice等
- reflect.Value.Type() 获取变量值的类型,等同于reflect.TypeOf()
- 反射可以将反射对象还原成 interface 对象
packagemainimport ( "fmt""reflect") funcmain(){ varxfloat64=3.4v :=reflect.ValueOf(x) //v is reflext.Valuevaryfloat64=v.Interface().(float64) fmt.Println("value", y) } //运行结果://value 3.4
对象 x 转换成反射对象 v,v 又通过 Interface() 接口转换成了 interface 对象,interface 对象通过.(float64)类型断言获取 float64 类型的值。
断言格式为:s = x.(T),意思是如果 x 所持有的元素如果同样实现了 T 接口,那么就把值传递给 s。
- 反射对象可修改,value值必须是可设置的
当使用 TypeOf() 和 ValueOf() 时,如果传递的不是接口变量的指针,那么反射里的变量值是一个副本值,对反射对象进行修改时,并不能修改真实的值。
错误示例:
packagemainimport ( "reflect") funcmain(){ varxfloat64=3.4v :=reflect.ValueOf(x) //v is reflext.Valuev.SetFloat(6.6) //Error}
上面的程序会发生 panic ,因为 v 是不可修改的。
- 传入 `reflect.ValueOf()` 函数的其实是 x 的副本值,而并非 x 本身。所以通过 v 修改其值是无法影响 x 的,所以会报错。
- 若要修改,我们可以在 ValueOf()中传入 x 的地址,此时 v 代表的是指针地址,如何通过指针地址 v 修改 x 的值呢?
- 使用 reflect.Value 的 Elem() 方法,可以获得指针指向的 value 。
示例:
packagemainimport ( "fmt""reflect") funcmain(){ varxfloat64=3.4v :=reflect.ValueOf(&x) v.Elem().SetFloat(6.6) fmt.Println("x :", v.Elem().Interface()) } //运行结果//x : 6.6