概念
Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。
为什么需要反射
反射常见应用常见有如下:
1、数据类型转换的场景,将struct类型转换为map[string]interface{}类型,要去遍历struct类型对应的key和value,则需要通过反射来进行遍历。
2、不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。例如以下这种桥接模式:
func bridge(funcPtr interface{}, args ...interface{})
反射规则定律
反射可以从接口值中获取反射对象的类别和值
-
获取反射对象的值,通过使用 reflect.ValueOf() 函数
type Event struct { ID string Name string } eventTypeName := reflect.TypeOf(Event{}).Kind().String() fmt.Printf("eventTypeName:%s \n", eventTypeName) -
获取反射对象的类别,使用 reflect.TypeOf() 函数
type Event struct { ID string Name string } eventValue := reflect.ValueOf(Event{}) fmt.Printf("demoTypeName:%+v \n", eventValue)
反射可以从反射对象中获得接口值
- 将 Value 转换成 interface,内部存放具体类型实例。使用 interface() 函数
type Event struct { ID string Name string } eventValue := reflect.ValueOf(Event{}).Interface() fmt.Printf("demoTypeName:%+v \n", eventValue)
Type和Value相互转换
- Type中只有类型信息,没有直接转换为Value相关的方法,可以通过reflect.New(typ Type)进行新建Value,得到一个指向 type 类型的指针,值是零值。
type Event struct { ID string Name string } func reflectFunc(src interface{}) { typ := reflect.TypeOf(src).Elem() value := reflect.New(typ) fmt.Printf("value:%+v", value) } func main() { reflectFunc(&Event{}) } - 由于反射对象 Value 中本来就存有 Tpye 的信息,所以 Value 向 Type 转换比较简单。
type Event struct { ID string Name string } func reflectFunc(src interface{}) { typ := reflect.ValueOf(src).Type() fmt.Printf("type:%+v", typ) } func main() { reflectFunc(&Event{}) }
Value 指针和值相互转换
- 把指针的 Value 转换成值 Value 有 2 个方法 Indirect() 和 Elem()。
type Event struct { ID string Name string } func reflectFunc(src interface{}) { // 通过Elem(),将指针类型转换为值类型 value1 := reflect.ValueOf(src).Elem() fmt.Printf("value1:%+v", value1) // 通过Indirect(),将指针类型转换为值类型 value2 := reflect.Indirect(reflect.ValueOf(src)) fmt.Printf("value2:%+v", value2) } func main() { reflectFunc(&Event{}) } - 将值 Value 转换成指针的 Value, 通过Addr()这一个方法。
type Event struct { ID string Name string } func reflectFunc(src interface{}) { value := reflect.ValueOf(src).Elem().Addr() fmt.Printf("将值类型转换为指针类型:%+v", value) } func main() { reflectFunc(&Event{ID: "001"}) }
Type指针类型和Type值的相互转换
-
指针类型 Type 转成值类型 Type。通过通过Elem()方法进行转换的类型为 Array、Chan、Map、Pointer 和 Slice 等类型,否则会发生 panic。Type 返回的是内部元素的 Type。
Elem()方法实现源码:
func (t *rtype) Elem() Type { switch t.Kind() { case Array: tt := (*arrayType)(unsafe.Pointer(t)) return toType(tt.elem) case Chan: tt := (*chanType)(unsafe.Pointer(t)) return toType(tt.elem) case Map: tt := (*mapType)(unsafe.Pointer(t)) return toType(tt.elem) case Pointer: tt := (*ptrType)(unsafe.Pointer(t)) return toType(tt.elem) case Slice: tt := (*sliceType)(unsafe.Pointer(t)) return toType(tt.elem) } panic("reflect: Elem of invalid type " + t.String()) }使用示例:
func reflectFunc(src interface{}) { typ := reflect.TypeOf(src).Elem() fmt.Printf("将指针类型转换为值类型:%+v", typ) } func main() { reflectFunc(map[string]string{"id": "001"}) } -
Type 值类型转换为 指针类型,通过reflect.PtrTo()方法
func reflectFunc(src interface{}) { typ := reflect.TypeOf(src).Elem() ptr := reflect.PtrTo(typ) fmt.Printf("将值类型转换为指针类型:%T", ptr) } func main() { reflectFunc(map[string]string{"id": "001"}) }
常见应用场景
反射获取 interface 的类型和值
type Event struct {
ID string
Name string
}
func reflectFunc(src interface{}) {
// 获取 interface 数据类别
typ := reflect.TypeOf(src).Elem().Name()
// 获取 interface 值
value := reflect.ValueOf(src)
fmt.Printf("type:%T, value:%+v", typ, value)
}
func main() {
reflectFunc(&Event{ID: "001", Name: "反射"})
}
反射获取 struct 的 tag 信息
提供方法:
StructTag 提供两个获取tag的方法
func (tag StructTag) Get(key string) string
func (tag StructTag) Lookup(key string) (value string, ok bool)
他们之间的区别是Lookup根据传入的key,进行咨询值是否存在
实现方式:
1、通过 reflect.TypeOf() 获取 struct 的 Type
2、通过 Field***() 方法获取 strcut 属性类别
3、根据属性类别的 Tag 属性的 Get(key string) 或 Lookup(key string) 方法获取tag
代码示例:
type Event struct {
ID string `json:"id"`
Name string `json:"name"`
}
func reflectFunc(src interface{}) {
typ := reflect.TypeOf(src)
for i := 0; i < typ.NumField(); i++ {
tag1 := typ.Field(i).Tag.Get("json")
fmt.Printf("tag1:%+v \n", tag1)
tag2, ok := typ.Field(i).Tag.Lookup("json")
if ok {
fmt.Printf("tag:%+v \n", tag2)
}
}
}
func main() {
reflectFunc(Event{ID: "001", Name: "反射"})
}
反射修改 interface 的值
实现方式:
通过 reflect.ValueOf(指针类型).Elem() 获取value值
通过 CanSet() 方法判断是否可以编辑
通过 Set***() 方法进行修改对应的值
代码示例:
type Event struct {
ID string
Name string
}
func reflectFunc(src interface{}) {
// 修改 interface 值
value := reflect.ValueOf(src).Elem()
if value.CanSet() {
value.FieldByName("Name").SetString("放射+1")
}
fmt.Printf("value:%+v", value)
}
func main() {
reflectFunc(&Event{ID: "001", Name: "反射"})
}
注意:通过setXXX()进行修改值的时候,reflect.ValueOf的值必须为指针类型,否则会报reflect.Value.SetString using unaddressable value的错误
通过反射调用方法
-
调用 struct 方法
实现方式:
1、通过 reflect.ValueOf() 方法获取反射的struct值
2、通过 MethodByName() 方法获取struct的方法
3、构建参数列表 []reflect.Value{reflect.ValueOf("***")}
4、通过 call() 方法执行struct方法 代码示例:type Event struct { ID string Name string } func (e *Event) Send(addr string) bool { fmt.Println("exec event.Send()") return true } func (e *Event) Close() { fmt.Println("exec event.Close()") } func reflectFunc(src interface{}) { value := reflect.ValueOf(src) // 反射执行有参函数 sendMethod := value.MethodByName("Send") result := sendMethod.Call([]reflect.Value{reflect.ValueOf("127.0.0.1")}) fmt.Printf("event send result:%+v\n", result) // 放射执行无参函数 closeMethod := value.MethodByName("Close") closeMethod.Call([]reflect.Value{}) } func main() { reflectFunc(&Event{ID: "001", Name: "反射"}) } -
调用普通函数
实现方式:
1、通过reflect.ValueOf(函数名),将函数转换value类别
2、构建参数列表 []reflect.Value{reflect.ValueOf("***")}
3、通过 call() 方法执行函数代码示例:
func SendEvent(eventName string) bool { fmt.Println("exec send event") return true } func main() { //reflectFunc(&Event{ID: "001", Name: "反射"}) sendEventMethod := reflect.ValueOf(SendEvent) params := []reflect.Value{reflect.ValueOf("反射调用普通函数")} result := sendEventMethod.Call(params) fmt.Printf("send event result:%v", result) }
判断反射值的空和有效性
提供方法:
| 方 法 | 说 明 |
|---|---|
| IsNil() bool | 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作 |
| IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 |
代码示例:
func main() {
// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
总结
反射是把双刃剑,功能强大但代码可读性并不好,而且反射是运行时动态的修改或执行某个变量或方法,一些问题不能在编译的时候提前暴露出来,增加了隐藏风险。若非必要并不推荐使用反射。