Go语言之反射
一、反射的基本概念
(一)什么是反射
- 反射可以再运行时动态获取变量的各种信息,比如变量的类型、值等
- 如果时结构体变量,还可以获取到结构体本身的各种信息,比如结构体的字段、方法
- 通过反射,还可以修改变量的值、调用方法
不过使用反射,需要引入一个包:reflect
典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。变量与反射类型之间的关系答题可以使用下图描述:
(二)反射的相关函数
1、reflect.Type类型
通过reflect.TypeOf函数对接收的任意数据类型进行反射,此时返回的类型,它是一个接口:
type Type interface { // Kind返回该接口的具体分类 Kind() Kind // Name返回该类型在自身包内的类型名,如果是未命名类型会返回"" Name() string // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64" // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回"" PkgPath() string // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64") // 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。 String() string // 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof Size() uintptr // 返回当从内存中申请一个该类型值时,会对齐的字节数 Align() int // 返回当该类型作为结构体的字段时,会对齐的字节数 FieldAlign() int // 如果该类型实现了u代表的接口,会返回真 Implements(u Type) bool // 如果该类型的值可以直接赋值给u代表的类型,返回真 AssignableTo(u Type) bool // 如该类型的值可以转换为u代表的类型,返回真 ConvertibleTo(u Type) bool // 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic Bits() int // 返回array类型的长度,如非数组类型将panic Len() int // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic Elem() Type // 返回map类型的键的类型。如非映射类型将panic Key() Type // 返回一个channel类型的方向,如非通道类型将会panic ChanDir() ChanDir // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic NumField() int // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic Field(i int) StructField // 返回索引序列指定的嵌套字段的类型, // 等价于用索引中每个值链式调用本方法,如非结构体将会panic FieldByIndex(index []int) StructField // 返回该类型名为name的字段(会查找匿名字段及其子字段), // 布尔值说明是否找到,如非结构体将panic FieldByName(name string) (StructField, bool) // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic FieldByNameFunc(match func(string) bool) (StructField, bool) // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真 // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片) // 如非函数类型将panic IsVariadic() bool // 返回func类型的参数个数,如果不是函数,将会panic NumIn() int // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic In(i int) Type // 返回func类型的返回值个数,如果不是函数,将会panic NumOut() int // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic Out(i int) Type // 返回该类型的方法集中方法的数目 // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法; // 匿名字段导致的歧义方法会滤除 NumMethod() int // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态 // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil Method(int) Method // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法 // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态 // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil MethodByName(string) (Method, bool) // 内含隐藏或非导出方法 }
所以如果获取到reflect.Type类型,然后调用其中的方法就可以获取被反射的数据类型的相应信息。
2、reflect.Value
这是一个结构体,为go值提供了反射接口:
type Value struct { // 内含隐藏或非导出字段 }
有很多的方法可供使用:
func (v Value) IsValid() bool func (v Value) IsNil() bool func (v Value) Kind() Kind func (v Value) Type() Type func (v Value) Convert(t Type) Value func (v Value) Elem() Value func (v Value) Bool() bool func (v Value) Int() int64 func (v Value) OverflowInt(x int64) bool func (v Value) Uint() uint64 func (v Value) OverflowUint(x uint64) bool func (v Value) Float() float64 func (v Value) OverflowFloat(x float64) bool func (v Value) Complex() complex128 func (v Value) OverflowComplex(x complex128) bool func (v Value) Bytes() []byte func (v Value) String() string func (v Value) Pointer() uintptr func (v Value) InterfaceData() [2]uintptr func (v Value) Slice(i, j int) Value func (v Value) Slice3(i, j, k int) Value func (v Value) Cap() int func (v Value) Len() int func (v Value) Index(i int) Value func (v Value) MapIndex(key Value) Value func (v Value) MapKeys() []Value func (v Value) NumField() int func (v Value) Field(i int) Value func (v Value) FieldByIndex(index []int) Value func (v Value) FieldByName(name string) Value func (v Value) FieldByNameFunc(match func(string) bool) Value func (v Value) Recv() (x Value, ok bool) func (v Value) TryRecv() (x Value, ok bool) func (v Value) Send(x Value) func (v Value) TrySend(x Value) bool func (v Value) Close() func (v Value) Call(in []Value) []Value func (v Value) CallSlice(in []Value) []Value func (v Value) NumMethod() int func (v Value) Method(i int) Value func (v Value) MethodByName(name string) Value func (v Value) CanAddr() bool func (v Value) Addr() Value func (v Value) UnsafeAddr() uintptr func (v Value) CanInterface() bool func (v Value) Interface() (i interface{}) func (v Value) CanSet() bool func (v Value) SetBool(x bool) func (v Value) SetInt(x int64) func (v Value) SetUint(x uint64) func (v Value) SetFloat(x float64) func (v Value) SetComplex(x complex128) func (v Value) SetBytes(x []byte) func (v Value) SetString(x string) func (v Value) SetPointer(x unsafe.Pointer) func (v Value) SetCap(n int) func (v Value) SetLen(n int) func (v Value) SetMapIndex(key, val Value) func (v Value) Set(x Value)
3、reflect.Type、reflect.Value类型与变量之间的转换
如果得到当前reflect.Value类型,那么如何将其转化为原始的变量类型,比如上述的User结构体类型:
package main import ( "fmt" "reflect" ) type User struct { NickName string Age int } func transType(v interface{}) { // 使用interface{}接收任意类型的反射 // 1、将interface{}转成reflect.Value类型 rVal := reflect.ValueOf(v) // 2、将reflect.Value转成interface{} iVal := rVal.Interface() // 3、通过断言将interface{}转成User类型 user := iVal.(User) fmt.Printf("原类型为%T", user) //原类型为main.User } func main() { user := User{ NickName: "lily", Age: 20, } transType(user) }
可以知道借助于中间interface{}变量完成转化:reflect.Value-->interface{}-->原类型
3、reflect.Value与reflect.Type转换
如果得到reflect.Value类型通过这个结构体中的 func (Value) Type 可以完成相互转化,从而调用reflect.Type类型中的方法:
package main import ( "fmt" "reflect" ) func transType(v interface{}) { // 使用interface{}接收任意类型的反射 // 1、将interface{}转成reflect.Value类型 rVal := reflect.ValueOf(v) // 2、将reflect.Value转成reflect.Type类型 rType := rVal.Type() // 3、调用reflect.Type类型中的方法,获取类别 kind := rType.Kind() fmt.Print(kind) } func main() { var num = 10 transType(num) }
(三)其它
1、Type和Kind之间的区别
Type是类型,Kind是类别,从范围上说,Kind的范畴更大,Type和Kind可能是相同的,也可能是不同的,比如:
var num int = 5 // Type是int类型,Kind也是int类型 var user User // user的Type是pkg.User,Kind是struct类型
2、通过反射获取变量的值
在上面通过变量、interface{}、reflect.Value可以进行转换,但是反射得到reflect.Value类型,并没有获取对应变量或者字段的值,所以这里看一下如何获取值。
package main import ( "fmt" "reflect" ) func transType(v interface{}) { // 使用interface{}接收任意类型的反射 // 1、将interface{}转成reflect.Value类型 rVal := reflect.ValueOf(v) // 2、将reflect.Value调用对应的方法获取值 value := rVal.Int() fmt.Println(value) // 10 } func main() { var num = 10 transType(num) }
通过反射获取变量的值,要求数据类型匹配,比如num是int类型,那么在反射中就需要使用reflect.ValueOf(v).Int()方法。
3、通过反射修改值
通过反射来修改变量的值,那么一定要传入变量的指针类型呢,这样才能修改原来的值。同时需要使用到reflect.ValueOf(v).Elem()方法
package main import ( "fmt" "reflect" ) func transSet(v interface{}) { // 使用interface{}接收任意类型的反射 // 1、将interface{}转成reflect.Value类型 rVal := reflect.ValueOf(v) // 2、将reflect.Value调用对应的方法修改值 rVal.Elem().SetInt(20) } func main() { var num = 10 transSet(&num) fmt.Println(num) // 20 }
在上面的reflect.ValueOf(v).Elem()可以理解为获取指针变量:
var num = 10 var value *int= &num *value = 20
二、反射最佳实践
通过反射获取结构体字段、方法及调用,并且获取字段标签:
package main import ( "fmt" "reflect" ) type User struct { UserName string `json:"username"` Age int `json:"age"` } func (u User) GetUser() { fmt.Println("获取用户信息") } func (u User) GetAge(age int, num int) int { lastAge := age + num return lastAge } func TestRefStru(v interface{}) { // 对结构体变量进行反射 // 获取reflect.Type类型 rType := reflect.TypeOf(v) // 获取reflect.Value类型 rVal := reflect.ValueOf(v) // 获取v对应的类别 kd := rVal.Kind() // 如果传入的非struct就退出 if kd != reflect.Struct { return } // 获取结构体中有多少个字段 FieldNum := rVal.NumField() // 对字段的tag进行反射,获取tag标签值 for i := 0; i < FieldNum; i++ { // 获取struct标签,通过reflect.Type来获取tag标签值 tagVal := rType.Field(i).Tag.Get("json") // 如果字段存在就打印出来 if tagVal != "" { fmt.Printf("tag:%v \n", tagVal) } } // 获取结构体中有多少个方法 MethodNum := rVal.NumMethod() fmt.Println("方法个数为:", MethodNum) // 方法排序是按照ASCII排序,与在结构体中的顺序无关 // 该方法无参数的调用 rVal.Method(1).Call(nil) //调用第一个方法 // 含参数的调用,注意Call方法传入的的[]reflect.Value类型的参数,返回值是[]reflect.Value类型 var parames []reflect.Value parames = append(parames, reflect.ValueOf(20)) parames = append(parames, reflect.ValueOf(5)) res := rVal.Method(0).Call(parames) fmt.Println("年龄", res[0].Int()) } func main() { // 创建一个User实例 u := User{ UserName: "lily", Age: 20, } TestRefStru(u) }