反射的定义
- 反射是指一类的应用,他们能够 自描述 自控制
- python反射 根据字符串执行函数、根据字符串导入包
go中反射的简介
- go是静态语言。反射就是go提供一种机制,在编译时不知道类型的情况下,可以做如下的事情
- 更新变量
- 运行时查看值
- 调用方法
- 对他们的布局进行操作
为什么使用反射
两个经典场景
- 你编写的这个函数,还不知道传给你的类型具体是什么,可能是还没约定好,也可能是传入的类型很多
- 希望通过用户的输入来决定调用按个函数(根据字符串调用方法),动态执行函数
- 举例使用 interface.type判断类型
package mainimport "fmt"func main() {var s interface{} = "abc"switch s.(type) {case string:fmt.Println("s.type=string")case int:fmt.Println("s.type=int")case bool:fmt.Println("s.type=bool")default:fmt.Println("未知的类型")}}
- 上述类型判断的问题
- 类型判断会写很多,代码很长
- 类型还会增删,不灵活
使用反射获取变量内部的信息
- reflect包提供 valueOf和Typeof
- reflect.ValueOf : 获取输入接口中数据的值,如果为空返回 0
- reflect.Typeof : 获取输入接口中值的类型,如果为空返回 nil
- Typeof传入所有类型,因为所有的类型都实现了空接口
举例1 内置类型的测试
package mainimport ("log""reflect")func main() {var s interface{} = "abc"// TypeOf会返回模板的对象reflectType := reflect.TypeOf(s)reflectValue := reflect.ValueOf(s)log.Printf("[typeof:%v]", reflectType)log.Printf("[valueof:%v]", reflectValue)}
举例2 自定义struct的反射
- 生成的举例,对未知类型的进行 遍历探测它的Field,抽象成一个函数
- go语言里面struct成员变量小写,在反射的时候直接panic
reflect.Value.Interface: cannot return value obtained from unexported field or method - 结构体方法名小写是不会panic,反射时也不会被查看到
- 指针方法是不能被反射查看到的
对于成员变量
- 先获取intereface的reflect.Type,然后遍历NumField
- 再通过reflect.Type的Field获取字段
- 最后通过Field的interface获取对应的value
对于方法
- 先获取intereface的reflect.Type,然后遍历NumMethod
- 再分别通过reflect.Type的 t.Method获取真实的方法
- 最后通过Name和Type获取方法的类型和值
package mainimport ("log""reflect")type Person struct {Name stringAge int}type Student struct {Person //匿名结构体嵌套StudentId intSchoolName stringIsBaoSong bool //是考上来的吗Hobbies []string// panic: reflect.Value.Interface: cannot return value obtained from unexported field or method//hobbies []stringLabels map[string]string}func (s *Student) GoHome() {log.Printf("[回家了][sid:%d]", s.StudentId)}func (s Student) GotoSchool() {log.Printf("[去上学了][sid:%d]", s.StudentId)}func (s Student) baosong() {log.Printf("[竞赛保送][sid:%d]", s.StudentId)}func main() {s := Student{Person: Person{Name: "xiaoyi", Age: 9900},StudentId: 123,SchoolName: "五道口皇家男子职业技术学院",IsBaoSong: true,Hobbies: []string{"唱", "跳", "Rap"},//hobbies: []string{"唱", "跳", "Rap"},Labels: map[string]string{"k1": "v1", "k2": "v2"},}// 获取目标对象t := reflect.TypeOf(s)log.Printf("[对象的类型名称:%s]", t.Name())// 获取目标对象的值类型v := reflect.ValueOf(s)// 遍历获取成员变量for i := 0; i < t.NumField(); i++ {// Field 代表对象的字段名key := t.Field(i)value := v.Field(i).Interface()//if key.Anonymous {log.Printf("[匿名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)} else {log.Printf("[命名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)}}// 打印方法for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)log.Printf("[第:%d个方法][方法名称:%s][方法的类型:%v]", i+1, m.Name, m.Type)}/*2021/07/10 15:21:07 [对象的类型名称:Student]2021/07/10 15:21:07 [匿名字段][第:1个字段][字段名:Person][字段的类型:main.Person][字段的值:{xiaoyi 9900}]2021/07/10 15:21:07 [命名字段][第:2个字段][字段名:StudentId][字段的类型:int][字段的值:123]2021/07/10 15:21:07 [命名字段][第:3个字段][字段名:SchoolName][字段的类型:string][字段的值:五道口皇家男子职业技术学院]2021/07/10 15:21:07 [命名字段][第:4个字段][字段名:IsBaoSong][字段的类型:bool][字段的值:true]2021/07/10 15:21:07 [命名字段][第:5个字段][字段名:Hobbies][字段的类型:[]string][字段的值:[唱 跳 Rap]]2021/07/10 15:21:07 [命名字段][第:6个字段][字段名:Labels][字段的类型:map[string]string][字段的值:map[k1:v1 k2:v2]]2021/07/10 15:21:07 [第:1个方法][方法名称:GotoSchool][方法的类型:func(main.Student)]*/}
- 抽成一个函数
package mainimport ("log""reflect")type Person struct {Name stringAge int}type Student struct {Person //匿名结构体嵌套StudentId intSchoolName stringIsBaoSong bool //是考上来的吗Hobbies []string// panic: reflect.Value.Interface: cannot return value obtained from unexported field or method//hobbies []stringLabels map[string]string}//func (s *Student) GoHome() {// log.Printf("[回家了][sid:%d]", s.StudentId)//}func (s Student) GoHome() {log.Printf("[回家了][sid:%d]", s.StudentId)}func (s Student) GotoSchool() {log.Printf("[去上学了][sid:%d]", s.StudentId)}func (s Student) Baosong() {log.Printf("[竞赛保送][sid:%d]", s.StudentId)}func main() {s := Student{Person: Person{Name: "xiaoyi", Age: 9900},StudentId: 123,SchoolName: "五道口皇家男子职业技术学院",IsBaoSong: true,Hobbies: []string{"唱", "跳", "Rap"},//hobbies: []string{"唱", "跳", "Rap"},Labels: map[string]string{"k1": "v1", "k2": "v2"},}p := Person{Name: "李逵",Age: 124,}reflectProbeStruct(s)reflectProbeStruct(p)}func reflectProbeStruct(s interface{}) {// 获取目标对象t := reflect.TypeOf(s)log.Printf("[对象的类型名称:%s]", t.Name())// 获取目标对象的值类型v := reflect.ValueOf(s)// 遍历获取成员变量for i := 0; i < t.NumField(); i++ {// Field 代表对象的字段名key := t.Field(i)value := v.Field(i).Interface()//if key.Anonymous {log.Printf("[匿名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)} else {log.Printf("[命名字段][第:%d个字段][字段名:%s][字段的类型:%v][字段的值:%v]", i+1, key.Name, key.Type, value)}}// 打印方法for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)log.Printf("[第:%d个方法][方法名称:%s][方法的类型:%v]", i+1, m.Name, m.Type)}/*2021/07/10 15:21:07 [对象的类型名称:Student]2021/07/10 15:21:07 [匿名字段][第:1个字段][字段名:Person][字段的类型:main.Person][字段的值:{xiaoyi 9900}]2021/07/10 15:21:07 [命名字段][第:2个字段][字段名:StudentId][字段的类型:int][字段的值:123]2021/07/10 15:21:07 [命名字段][第:3个字段][字段名:SchoolName][字段的类型:string][字段的值:五道口皇家男子职业技术学院]2021/07/10 15:21:07 [命名字段][第:4个字段][字段名:IsBaoSong][字段的类型:bool][字段的值:true]2021/07/10 15:21:07 [命名字段][第:5个字段][字段名:Hobbies][字段的类型:[]string][字段的值:[唱 跳 Rap]]2021/07/10 15:21:07 [命名字段][第:6个字段][字段名:Labels][字段的类型:map[string]string][字段的值:map[k1:v1 k2:v2]]2021/07/10 15:21:07 [第:1个方法][方法名称:GotoSchool][方法的类型:func(main.Student)]*/}
举例3 反射修改值
- 必须是指针类型
- pointer.Elem().Setxxx()
package mainimport ("log""reflect")func main() {var num float64 = 3.14log.Printf("[num原始值:%f]",num)// 通过reflect.ValueOf获取num中的value// 必须是指针才可以修改值pointer:=reflect.ValueOf(&num)newValue:=pointer.Elem()// 赋值newValue.SetFloat(5.6)log.Printf("[num新值:%f]",num)pointer = reflect.ValueOf(num)// reflect: call of reflect.Value.Elem on float64 ValuenewValue = pointer.Elem()}
举例4 反射调用方法
- 过程说明
- 首先reflect.ValueOf(p1)获取 得到反射类型对象
- reflect.ValueOf.MethodByName ,需要传入准确的方法名称,MethodByName代表注册
- 名字错了 会panic: reflect: call of reflect.Value.Call on zero Value
- []reflect.Value ,这是最终需要调用方法的参数,无参数传空切片
package mainimport ("log""reflect")type Person struct {Name stringAge intGender string}func (p Person) ReflectCallFuncWithArgs(name string, age int) {log.Printf("[调用的是带参数的方法][args.name:%s][args.age:%d][[p.name:%s][p.age:%d]",name,age,p.Name,p.Age,)}func (p Person) ReflectCallFuncWithNoArgs() {log.Printf("[调用的是不带参数的方法]")}func main() {p1 := Person{Name: "小乙",Age: 18,Gender: "男",}// 1. 首先通过 reflect.ValueOf(p1)获取 得到反射值类型getValue := reflect.ValueOf(p1)// 2. 带参数的方法调用methodValue := getValue.MethodByName("ReflectCallFuncWithArgs")// 参数是reflect.Value的切片args := []reflect.Value{reflect.ValueOf("李逵"), reflect.ValueOf(30)}methodValue.Call(args)// 3. 不带参数的方法调用methodValue = getValue.MethodByName("ReflectCallFuncWithNoArgs")// 参数是reflect.Value的切片args = make([]reflect.Value, 0)methodValue.Call(args)}
结构体标签和反射
- json的标签解析json
- yaml的标签解析yaml
- xorm gorm的标签 标识db字段
- 自定义标签
- 原理是t.Field.Tag.Lookup(“标签名”)
- 混合的例子如下
package mainimport ("encoding/json""gopkg.in/yaml.v2""io/ioutil""log""reflect")type Person struct {Name string `json:"name" yaml:"yaml_name" mage:"name"`Age int `json:"age" yaml:"yaml_age" mage:"age"`City string `json:"-" yaml:"yaml_city" mage:"-"`}//json解析func jsonWork() {// 对象marshal成字符串p := Person{Name: "xiaoyi",Age: 18,City: "北京",}data, err := json.Marshal(p)if err != nil {log.Printf("[json.marshal.err][err:%v]", err)return}log.Printf("[person.marshal.res][res:%v]", string(data))// 从字符串解析成结构体p2Str := `{"name":"李逵","age":28,"city":"山东"}`var p2 Personerr = json.Unmarshal([]byte(p2Str), &p2)if err != nil {log.Printf("[json.unmarshal.err][err:%v]", err)return}log.Printf("[person.unmarshal.res][res:%v]", p2)}// yaml读取文件func yamlWork() {filename := "a.yaml"content, err := ioutil.ReadFile(filename)if err != nil {log.Printf("[ioutil.ReadFile.err][err:%v]", err)return}p := &Person{}//err = yaml.Unmarshal(content, p)err = yaml.UnmarshalStrict(content, p)if err != nil {log.Printf("[yaml.UnmarshalStrict.err][err:%v]", err)return}log.Printf("[yaml.UnmarshalStrict.res][res:%v]", p)}func jiexizidingyibiaoqian(s interface{}) {// typeOf type类型r := reflect.TypeOf(s)value := reflect.ValueOf(s)for i := 0; i < r.NumField(); i++ {field := r.Field(i)key := field.Nameif tag, ok := field.Tag.Lookup("mage"); ok {if tag == "-" {continue}log.Printf("[找到了mage标签][key:%v][value:%v][标签:mage=%s]",key,value.Field(i),tag,)}}}func main() {//jsonWork()//yamlWork()p := Person{Name: "xiaoyi",Age: 18,City: "北京",}jiexizidingyibiaoqian(p)}
反射的副作用
1.代码可读性变差
2.隐藏的错误躲过编译检查
- go静态语言,编译器能发现类型的错误
- 但是对于反射代码是无能为力的,可能运行很久才会panic
- 反射调用方法的副作用,将string参数传成 int
panic: reflect: Call using float64 as type intgoroutine 1 [running]:reflect.Value.call(0x31c260, 0xc0000783c0, 0x293, 0x328479, 0x4, 0xc00011df48, 0x2, 0x2, 0x353a00, 0xc0000200c0, ...)C:/Program Files/Go/src/reflect/value.go:406 +0x1337reflect.Value.Call(0x31c260, 0xc0000783c0, 0x293, 0xc00011df48, 0x2, 0x2, 0xc0000783c0, 0x293, 0xc000047f30)C:/Program Files/Go/src/reflect/value.go:337 +0xc5main.main()D:/nyy_work/go_path/src/maday06/reader.go:41 +0x316
3. go反射性能问题
type := reflect.value(obj)fieldValue:=type_.FieldByName("xx")
- 每次取出的fieldValue类型是reflect.value
- 它是一个具体的值,不是一个可复用的对象
- 每次反射都要malloc这个reflect.Value结构退,还有GC
- 反射比正常的代码要慢1-2个数据量级,如果是追求性能的关键模块应减少反射
