反射允许程序运行时对程序本身进行访问和修改的能力。反射主要是空接口(interface{})存储数据上,空接口(interface{})可以表达任意类型的数据,那我们如何知道这个空接口保存的数据值和数据类型了?反射就是在运行时动态的获取一个变量的类型和值。
在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型,一个是具体类型对应的值。Golang中的reflect包实现了运行时反射,通过调用TypeOf函数返回一个Type类型值,该值代表运行时的数据类型,调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。
TypeOf
使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。TypeOf(nil)会返回nil。
func TypeOf(i interface{}) Type
eg:
package mainimport ("fmt""reflect")type Foo struct {Bar string}func main() {var s ="hello world"fmt.Println(reflect.TypeOf(s))a := 3.14fmt.Println(reflect.TypeOf(a))b := 2fmt.Println(reflect.TypeOf(b))f := Foo{Bar:"bar"}fmt.Println(reflect.TypeOf(f))}
打印结果
stringfloat64intmain.Foo
通用方法
func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。func (t *rtype) NumField() int //返回一个struct 类型 的属性个数,如果非struct类型会抛异常func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作//注意对于:数组、切片、映射、通道、指针、接口func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型
Kind
Value.Type()和Value.Kind()这两个方法都可以获取对象或者变量的类型,如果是变量的话,使用这两个方法获取到的类型都是一样,差别是结构体对象。s’q下面的例子中f 实际类型是Foo,对应的底层数据类型是struct结构体类型,通过种Kind方法就可以获取到底层的类型,特别是当需要区分指针、结构体等大品种的类型时,kind 的方式就显得尤为重要。
package mainimport ("fmt""reflect")type Foo struct {Bar string}func reflectType(x interface{}) {t := reflect.TypeOf(x)fmt.Printf("type:%v,name:%v,kind:%v\n",t, t.Name(), t.Kind())}func main() {f := Foo{Bar:"bar"}reflectType(f)reflectType(&f)list := []string{"q","w"}reflectType(list)m:= make(map[string]string)m["a"]= "a"reflectType(m)}
注意Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。上面语句执行结果
type:main.Foo,name:Foo,kind:structtype:*main.Foo,name:,kind:ptrtype:[]string,name:,kind:slicetype:map[string]string,name:,kind:map
在reflect包中定义的Kind类型如下:
type Kind uintconst (Invalid Kind = iota // 非法类型Bool // 布尔型Int // 有符号整型Int8 // 有符号8位整型Int16 // 有符号16位整型Int32 // 有符号32位整型Int64 // 有符号64位整型Uint // 无符号整型Uint8 // 无符号8位整型Uint16 // 无符号16位整型Uint32 // 无符号32位整型Uint64 // 无符号64位整型Uintptr // 指针Float32 // 单精度浮点数Float64 // 双精度浮点数Complex64 // 64位复数类型Complex128 // 128位复数类型Array // 数组Chan // 通道Func // 函数Interface // 接口Map // 映射Ptr // 指针Slice // 切片String // 字符串Struct // 结构体UnsafePointer // 底层指针)
Elem()
获取元素类型、获取指针所指对象类型,获取接口的动态类型。
eg:
package mainimport ("fmt""reflect")type UserInfo struct {Name stringAge int}func main() {u := &UserInfo{Name: "ming",Age: 18,}userType := reflect.TypeOf(u)fmt.Println(userType.Kind(), userType.Name())elem := userType.Elem()fmt.Println(elem.Kind(), elem.Name())}
可以通过reflect.Elem()获取这个指针指向元素的类型,
ptrstruct UserInfo
ValueOf
ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。
func ValueOf(i interface{}) Value
获取变量的值:
reflect.ValueOf(x).Int()reflect.ValueOf(x).Float()reflect.ValueOf(x).String()reflect.ValueOf(x).Bool()
eg:
package mainimport ("fmt""reflect")type Foo struct {Bar string}func reflectValue(i interface{}){v :=reflect.ValueOf(i)k :=v.Kind()switch k {case reflect.Int:fmt.Printf("type is int value is %d\n",v.Int())case reflect.String:fmt.Printf("type is string value is %s\n",v.String())default:fmt.Printf("type is %v,value is %v\n",k,v)}}func main() {a := 10reflectValue(a)s := "hello"reflectValue(s)f := Foo{Bar:"bar"}reflectValue(f)}
打印结果
type is int value is 10type is string value is hellotype is struct,value is {bar}
修改值
通过反射的来改变变量的值reflect.Value.SetXX相关方法
reflect.Value.SetInt(),//设置整数reflect.Value.SetFloat(),//设置浮点数reflect.Value.SetString(),//设置字符串
SetXX(x) 因为传递的是 x 的值的副本,所以SetXX不能够改 x,否则会引起panic
func main() {var a = 2v := reflect.ValueOf(a)v.SetInt(3)fmt.Println(a)}
执行上面程序会出现下面错误
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
通过反射修改值的步骤如下:
1.reflect.ValueOf函数返回的是一份值的拷贝,所以前提是我们是传入要修改变量的地址,也就是必须向函数传递 x 的指针,SetXX(&x) 。
2 获取指针的值需要通过Elem函数
func main() {var a = 2v := reflect.ValueOf(&a)v.Elem().SetInt(3)fmt.Println(a)}
Indirect
取指针里面的值,和Elem性质差不多, 也是修改值
package mainimport ("fmt""reflect")func main() {var i =1value :=reflect.ValueOf(&i)value = reflect.Indirect(value)fmt.Println(value.Interface())if value.Kind() == reflect.Int{value.SetInt(2)}fmt.Println(value.Interface())}
上面打印结果
12
修改结构体
package mainimport ("fmt""reflect")type Bar struct {Foo string}func main() {b:=Bar{"hello"}value := reflect.ValueOf(&b)value = reflect.Indirect(value)fmt.Println(value.Interface())f:= value.FieldByName("Foo")if f.Kind()==reflect.String&&f.CanSet(){f.SetString("world")}fmt.Println(f)fmt.Println(value.Interface())}
IsNil
IsNil()主要被用于判断指针是否为空。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic,下面是IsNil的源码
func (v Value) IsNil() bool {k := v.kind()switch k {case Chan, Func, Map, Ptr, UnsafePointer:if v.flag&flagMethod != 0 {return false}ptr := v.ptrif v.flag&flagIndir != 0 {ptr = *(*unsafe.Pointer)(ptr)}return ptr == nilcase Interface, Slice:// Both interface and slice are nil if first word is 0.// Both are always bigger than a word; assume flagIndir.return *(*unsafe.Pointer)(v.ptr) == nil}panic(&ValueError{"reflect.Value.IsNil", v.kind()})}
package mainimport ("fmt""reflect")type Foo struct {Bar string}func main() {var s *stringfmt.Println(reflect.ValueOf(s).IsNil())//truef := Foo{Bar:"bar"}fmt.Println(reflect.ValueOf(&f).IsNil())//false}
IsValid
IsValid()常被用于判定返回值是否有效
package mainimport ("fmt""reflect")type Foo struct {Bar string}func main() {fmt.Println(reflect.ValueOf(nil).IsValid())var s *stringfmt.Println(reflect.ValueOf(s).IsValid())f :=Foo{Bar:"bar"}fmt.Println(reflect.ValueOf(f).FieldByName("Bar").IsValid())fmt.Println(reflect.ValueOf(f).MethodByName("Bar").IsValid())}
执行结果
falsetruetruefalse
反射应用
成员变量
| 方法 | 说明 |
|---|---|
| Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
| NumField() int | 返回结构体成员字段数量。 |
| FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
| FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
| FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
StructField 结构函数
ype StructField struct {Name string // 字段名PkgPath string // 字段路径Type Type // 字段反射类型对象Tag StructTag // 字段的结构体标签Offset uintptr // 字段在结构体中的相对偏移Index []int // Type.FieldByIndex中的返回的索引值Anonymous bool // 是否为匿名字段}
eg
package mainimport ("fmt""reflect")type UserInfo struct {Name stringAge int}func main() {u := &UserInfo{}userType := reflect.TypeOf(u)elem := reflect.New(userType.Elem()).Elem()elem.FieldByName("Name").SetString("tony")elem.FieldByName("Age").SetInt(20)fmt.Println(elem.Field(0),elem.Field(1))}
打印结果
tony 20
字段遍历
Name是字段的名字。
PkgPath是非导出字段的包路径,对导出字段该字段为””
package mainimport ("fmt""reflect")type Student struct {Name string `orm:"name"`Age int `orm:"age"`}func (s *Student)Update(){s.Age+=1}func (s Student)Show(){fmt.Println(s.Name,s.Age)}func (s Student)print(){fmt.Println(s.Name,s.Age)}func main() {s := Student{Name:"BX",Age:18}t :=reflect.TypeOf(s)for i:=0;i<t.NumField();i++{field := t.Field(i)fmt.Printf("name=%s PkgPath=%s index=%d type=%v tag=%v Offset=%v Anonymous=%v\n", field.Name,field.PkgPath, field.Index, field.Type, field.Tag.Get("orm"),field.Offset,field.Anonymous)}p :=reflect.TypeOf(&s)for i :=0;i<p.NumMethod();i++{method := p.Method(i)fmt.Printf("name=%s PkgPath=%s index=%d type=%v func=%v \n", method.Name,method.PkgPath, method.Index, method.Type, method.Func)}}
执行结果
动态创建对象
动态调用方法
| 方法 | 说明 |
|---|---|
| NumMethod() int | 返回该类型的方法集中方法的数目 |
| Method(int) Method | 返回该类型方法集中的第i个方法 |
| MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
通过MethodByName来进行方法的调用
package mainimport ("fmt""reflect")type User struct {name stringage int}func (u *User)update(name string,age int){u.name = nameu.age = age}func(u *User)Show(){fmt.Printf("name=%s,age=%d\n",u.name,u.age)}func main() {u := User{name: "tony",age: 10,}v := reflect.ValueOf(&u)name := reflect.ValueOf(&u.name)name.Elem().SetString("wang")update := v.MethodByName("update")if update.IsValid() {args :=[]reflect.Value{reflect.ValueOf("ellen"),reflect.ValueOf(20)}update.Call(args)}show := v.MethodByName("Show")if show.IsValid() {show.Call([]reflect.Value{})}}
执行结果
name=wang,age=10
Tag标签
结构体中的字段除了有名字和类型外,还可以有一个可选的标签。它是一个附属于字段的字符串,可以是文档或其它的重要标记。标签的内容不可以在一般的编程中使用,只有包reflect能获取它。reflect包可以在运行时自省类型、属性和方法,比如在一个变量上调用reflect.TypeOf()可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过Field来索引结构体的字段,然后就可以使用Tag属性
package mainimport ("reflect""fmt")type User struct { // tagsId int64 `json:"id"`Name string `json:"name"`Gender bool `json:"gender"`}func main() {u := User{10001, "ming", true}t := reflect.TypeOf(u)v := reflect.ValueOf(u)for i := 0; i < t.NumField(); i++ {fmt.Printf("%v=%v\n", t.Field(i).Tag.Get("json"),v.Field(i).Interface())}}
执行结果
id=10001name=minggender=true
接口判断
Implements(u Type) bool // 判断是否存在与 u 相同的接口
package mainimport ("fmt""reflect")//判断实例是否实现了某接口type Foo interface {show()}type Bar struct {}func (b *Bar) show() {fmt.Println("hello world")}func main() {b := new(Bar)f := reflect.TypeOf((*Foo)(nil)).Elem()tt := reflect.TypeOf(b)res := tt.Implements(f)fmt.Println(res)}
