1. 反射的概念
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用, 来源: https://juejin.im/post/6844903559335526407。
Golang 中一个变量包含两个部分组成:变量的值和变量的类型,类型有 int,string 等。在Golang 的一个interface变量,就是因为记录了这个变量原本的类型和值两个属性,所以才能用类型断言来区分类型。
反射是一把双刃剑,多存在与第三库或者框架中,比如 json和rpc 等,日常业务代码编写中很少使用反射,这个和反射的优缺点相关:
- 反射可以灵活的处理代码中变量
- 反射的性能很差
- 反射的代码比较脆弱,在运行时容易出现 panic 错误
- 反射的代码相对而言可读性比较差,不利于项目的维护
2. 反射中的TypeOf
Golang 反射中的TypeOf用于获取变量的类型信息,在TypeOf()得到的接口Type存在 Name() 和 Kind()两种类型,前者是具体的类型名称,后者kind为种类。如下面结构体student的实例e1,它的Kind为struct,name为Student。
需要注意的是,数组、切片、Map、指针等类型的变量的 Name() 为空字符串。
1. Type 接口type Type interface {// Kind返回该接口的具体分类Kind() Kind// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""Name() string// 返回array类型的长度,如非数组类型将panicLen() int// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panicElem() Type// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panicNumField() int// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panicField(i int) StructField// 返回索引序列指定的嵌套字段的类型,// 等价于用索引中每个值链式调用本方法,如非结构体将会panicFieldByIndex(index []int) StructField// 返回该类型名为name的字段(会查找匿名字段及其子字段),// 布尔值说明是否找到,如非结构体将panicFieldByName(name string) (StructField, bool)// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panicFieldByNameFunc(match func(string) bool) (StructField, bool)// 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真// 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)// 如非函数类型将panicIsVariadic() bool// 返回func类型的参数个数,如果不是函数,将会panicNumIn() int// 返回该类型的方法集中方法的数目// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;// 匿名字段导致的歧义方法会滤除NumMethod() int// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nilMethod(int) Method// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nilMethodByName(string) (Method, bool)// 内含隐藏或非导出方法,以及部分不常用方法}2. TypeOf()func TypeOf(i interface{}) Type获取参数 i 的类型信息3. Kind类型type Kind uintconst (Invalid Kind = iotaBoolIntInt8Int16Int32Int64UintUint8Uint16Uint32Uint64UintptrFloat32Float64Complex64Complex128ArrayChanFuncInterfaceMapPtrSliceStringStructUnsafePointer)
func getType(i interface{}) {t := reflect.TypeOf(i)fmt.Printf("value:%#v, typeof:%v, kind:%v, name:%v, type:%T\n", i, t, t.Kind(), t.Name(), t)}func main() {a1 := "ssss"getType(a1)b1 := int8(120)getType(b1)c1 := []int{1, 2, 3}getType(c1)d1 := map[int]string{1: "a"}getType(d1)type student struct {id stringname string}e1 := student{"s1001", "ZhangSan"}getType(e1)getType(&e1)}
[root@duduniao go_learn]# go run day24/reflect/type/main.govalue:"ssss", typeof:string, kind:string, name:string, type:*reflect.rtypevalue:120, typeof:int8, kind:int8, name:int8, type:*reflect.rtypevalue:[]int{1, 2, 3}, typeof:[]int, kind:slice, name:, type:*reflect.rtypevalue:map[int]string{1:"a"}, typeof:map[int]string, kind:map, name:, type:*reflect.rtypevalue:main.student{id:"s1001", name:"ZhangSan"}, typeof:main.student, kind:struct, name:student, type:*reflect.rtypevalue:&main.student{id:"s1001", name:"ZhangSan"}, typeof:*main.student, kind:ptr, name:, type:*reflect.rtype
3. 反射中的ValueOf
使用 ValueOf() 可以获取到对应的值结构体,并可以通过该结构体方法获取到需要内容。
1. Valuetype Value struct {// 内含隐藏或非导出字段}2. ValueOf()func ValueOf(i interface{}) Value获取变量 i 的值信息,该信息存储在 Value 的结构体实例中3. Kind()func (v Value) Kind() KindKind返回v持有的值的分类,如果v是Value零值,返回值为Invalid4. Type()func (v Value) Type() Type返回v持有的值的类型的Type表示。5. Elem()func (v Value) Elem() ValueElem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。6. 转换为go中具体类型func (v Value) Bool() boolfunc (v Value) Int() int64func (v Value) Uint() uint64func (v Value) Float() float64func (v Value) Pointer() uintptrfunc (v Value) Bytes() []bytefunc (v Value) String() stringfunc (v Value) Interface() (i interface{})7. 判断func (v Value) IsValid() boolIsValid返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。func (v Value) IsNil() boolIsNil报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。注意IsNil并不总是等价于go语言中值与nil的常规比较。例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。
3.1. 通过反射获取值
func getValue(i interface{}) {v := reflect.ValueOf(i)switch v.Kind() {case reflect.Int:fmt.Printf("type: int, value:%d, valueType: %T\n", int(v.Int()), v)case reflect.String:fmt.Printf("type: string, value:%s, valueType: %T\n", v.String(), v)}}func main() {a2 := 123456getValue(a2)b2 := "Hello world"getValue(b2)}
3.2. 通过反射设置值
通过函数修改值,必须要传入参数得指针,否则无法修改成功。
func setValue(i interface{}) {v := reflect.ValueOf(i)if v.Elem().Kind() == reflect.Int {v.Elem().Set(reflect.ValueOf(1024))}}func main() {a2 := 1000setValue(&a2)fmt.Printf("type:%T, value:%d\n", a2, a2)}
4. 结构体类型的反射
结构体类型在反射中用的比较多,比如解析 ini 文件,解析 json ,gorm 操作等。一个是获取结构体中得字段信息,一个是获取结构体方法信息。
4.1. 获取结构体字段信息
1. StructFieldtype StructField struct {// Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。Name stringPkgPath stringType Type // 字段的类型Tag StructTag // 字段的标签Offset uintptr // 字段在结构体中的字节偏移量Index []int // 用于Type.FieldByIndex时的索引切片Anonymous bool // 是否匿名字段}2. 在TypeOf()获取得Type类型具备以下结构体相关方法// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panicNumField() int// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panicField(i int) StructField// 返回索引序列指定的嵌套字段的类型,// 等价于用索引中每个值链式调用本方法,如非结构体将会panicFieldByIndex(index []int) StructField// 返回该类型名为name的字段(会查找匿名字段及其子字段),// 布尔值说明是否找到,如非结构体将panicFieldByName(name string) (StructField, bool)// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panicFieldByNameFunc(match func(string) bool) (StructField, bool)3. Value 类型包含了以下与结构体相关得方法(1) NumField()func (v Value) NumField() int返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic(2) Field()func (v Value) Field(i int) Value返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic(3) FieldByIndex()func (v Value) FieldByIndex(index []int) Value返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic(4) FieldByName()func (v Value) FieldByName(name string) Value返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。(5) FieldByNameFunc()func (v Value) FieldByNameFunc(match func(string) bool) Value返回该类型第一个字段名满足match的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。4. 获取tag中字段func (tag StructTag) Get(key string) string
type Host struct {UUID string `json:"uuid"`IP string `json:"ip"`User string `json:"user"`Password string `json:"password"`}func getFieldByType(s interface{}) {t := reflect.TypeOf(s)if t.Name() != "Host" {fmt.Printf("Type error, need Host,recv %s\n", t.Name())return}// 遍历所有字段for i := 0; i < t.NumField(); i++ {fmt.Printf("file_name:%s, tag:%#v, json:%s\n", t.Field(i).Name, t.Field(i).Tag, t.Field(i).Tag.Get("json"))}// 根据字段名找字段信息if name, ok := t.FieldByName("IP"); ok {fmt.Printf("file_name:%s, tag:%#v\n", name.Name, name.Tag)}}func getFieldByValue(s interface{}) {v := reflect.ValueOf(s)if v.Kind() != reflect.Struct {fmt.Printf("Type error, need Host,recv %s\n", v.Kind().String())return}// 遍历所有字段for i := 0; i < v.NumField(); i++ {fmt.Printf("value:%v\n", v.Field(i).String())}// 根据字段名找字段信息fmt.Printf("value:%s\n", v.FieldByName("IP").String())}func main() {ubuntu007010 := Host{UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",IP: "10.4.7.10",User: "root",Password: "123456",}getFieldByType(ubuntu007010)getFieldByValue(ubuntu007010)}
[root@duduniao go_learn]# go run day24/reflect/struct/main.gofile_name:UUID, tag:"json:\"uuid\"", json:uuidfile_name:IP, tag:"json:\"ip\"", json:ipfile_name:User, tag:"json:\"user\"", json:userfile_name:Password, tag:"json:\"password\"", json:passwordfile_name:IP, tag:"json:\"ip\""value:6fae9e37-2cc0-41a6-8e43-f1201def9eccvalue:10.4.7.10value:rootvalue:123456value:10.4.7.10
4.2. 获取结构体方法
1. 在TypeOf()获取得Type类型具备以下结构体相关方法// 返回该类型的方法集中方法的数目// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;// 匿名字段导致的歧义方法会滤除NumMethod() int// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nilMethod(int) Method// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nilMethodByName(string) (Method, bool)2. Value 类型包含了以下与结构体相关得方法(1) NumMethod()func (v Value) NumMethod() int返回v持有值的方法集的方法数目。(2) Method()func (v Value) Method(i int) Value返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。(3) MethodByName()func (v Value) MethodByName(name string) Value返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值(4) 方法调用func (v Value) Call(in []Value) []Value通过Call调用方法时,参数必须是 []Value 切片
type Host struct {UUID string `json:"uuid"`IP string `json:"ip"`User string `json:"user"`Password string `json:"password"`}func (h Host) Info() {if marshal, err := json.Marshal(h); err != nil {fmt.Println(marshal)}}func (h Host) InitHost(tool string) {fmt.Printf("Init host %s by tool:%s\n", h.UUID, tool)}func getMethodByType(s interface{}) {typeOf := reflect.TypeOf(s)if typeOf.Kind() != reflect.Struct {return}// 遍历method, 如果是指针接收者方法,此处会有异常for i := 0; i < typeOf.NumMethod(); i++ {name := typeOf.Method(i).Namet := typeOf.Method(i).Type.String()fmt.Printf("method name:%s, type:%#v\n", name, t)}// 根据方法名称获取方法if fun, ok := typeOf.MethodByName("Info"); ok {fmt.Printf("method name:%s, type:%#v\n", fun.Name, fun.Type.Name())}}func getMethodByValue(s interface{}) {value := reflect.ValueOf(s)if value.Kind() != reflect.Struct {return}args := []reflect.Value{reflect.ValueOf("ansible")}value.MethodByName("InitHost").Call(args)}func main() {ubuntu007010 := Host{UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",IP: "10.4.7.10",User: "root",Password: "123456",}getMethodByType(ubuntu007010)getMethodByValue(ubuntu007010)}
[root@duduniao go_learn]# go run day24/reflect/struct/main.gomethod name:Info, type:"func(main.Host)"method name:InitHost, type:"func(main.Host, string)"method name:Info, type:""Init host 6fae9e37-2cc0-41a6-8e43-f1201def9ecc by tool:ansible
