反射允许程序运行时对程序本身进行访问和修改的能力。反射主要是空接口(interface{})存储数据上,空接口(interface{})可以表达任意类型的数据,那我们如何知道这个空接口保存的数据值和数据类型了?反射就是在运行时动态的获取一个变量的类型和值。
在Go的反射定义中,任何接口都会由两部分组成的,一个是接口的具体类型,一个是具体类型对应的值。Golang中的reflect包实现了运行时反射,通过调用TypeOf函数返回一个Type类型值,该值代表运行时的数据类型,调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。
TypeOf
使用reflect.TypeOf()
函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。TypeOf(nil)会返回nil。
func TypeOf(i interface{}) Type
eg:
package main
import (
"fmt"
"reflect"
)
type Foo struct {
Bar string
}
func main() {
var s ="hello world"
fmt.Println(reflect.TypeOf(s))
a := 3.14
fmt.Println(reflect.TypeOf(a))
b := 2
fmt.Println(reflect.TypeOf(b))
f := Foo{Bar:"bar"}
fmt.Println(reflect.TypeOf(f))
}
打印结果
string
float64
int
main.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 main
import (
"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:struct
type:*main.Foo,name:,kind:ptr
type:[]string,name:,kind:slice
type:map[string]string,name:,kind:map
在reflect
包中定义的Kind类型如下:
type Kind uint
const (
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 main
import (
"fmt"
"reflect"
)
type UserInfo struct {
Name string
Age 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()
获取这个指针指向元素的类型,
ptr
struct 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 main
import (
"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 := 10
reflectValue(a)
s := "hello"
reflectValue(s)
f := Foo{Bar:"bar"}
reflectValue(f)
}
打印结果
type is int value is 10
type is string value is hello
type 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 = 2
v := 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 = 2
v := reflect.ValueOf(&a)
v.Elem().SetInt(3)
fmt.Println(a)
}
Indirect
取指针里面的值,和Elem性质差不多, 也是修改值
package main
import (
"fmt"
"reflect"
)
func main() {
var i =1
value :=reflect.ValueOf(&i)
value = reflect.Indirect(value)
fmt.Println(value.Interface())
if value.Kind() == reflect.Int{
value.SetInt(2)
}
fmt.Println(value.Interface())
}
上面打印结果
1
2
修改结构体
package main
import (
"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.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
return ptr == nil
case 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 main
import (
"fmt"
"reflect"
)
type Foo struct {
Bar string
}
func main() {
var s *string
fmt.Println(reflect.ValueOf(s).IsNil())//true
f := Foo{Bar:"bar"}
fmt.Println(reflect.ValueOf(&f).IsNil())//false
}
IsValid
IsValid()常被用于判定返回值是否有效
package main
import (
"fmt"
"reflect"
)
type Foo struct {
Bar string
}
func main() {
fmt.Println(reflect.ValueOf(nil).IsValid())
var s *string
fmt.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())
}
执行结果
false
true
true
false
反射应用
成员变量
方法 | 说明 |
---|---|
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 main
import (
"fmt"
"reflect"
)
type UserInfo struct {
Name string
Age 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 main
import (
"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 main
import (
"fmt"
"reflect"
)
type User struct {
name string
age int
}
func (u *User)update(name string,age int){
u.name = name
u.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 main
import (
"reflect"
"fmt"
)
type User struct { // tags
Id 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=10001
name=ming
gender=true
接口判断
Implements(u Type) bool // 判断是否存在与 u 相同的接口
package main
import (
"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)
}