上篇使用 toml 统一管理 echo 的路由和中间件,核心的映射操作就是使用 reflect 完成的,这篇文章就来深究一下反射。
定义 Golang 中变量的类型分为 2 种:
static type:静态类型,在编译前后都是确定的,比如 int、string 等
concrete type:具体类型,在程序运行时才知道的类型,比如与反射密切相关的 interface{}
interface{}
类型的变量由 2 个部分组成:变量的实际类型、变量的实际值
反射:在程序运行时用来检测类型、值的一种机制
接口变量的两个组成部分 Type 使用 reflect.TypeOf(v)
可在运行时动态的获取接口变量的类型:
1 2 3 func TypeOf (i interface {}) Type {}
使用 Kind() Kind
返回分类的值类型:基础类型 bool、string,数字类型,聚合类型array、struct,引用类型 chan、ptr、func、slice、map,接口类型 interface,无任何值的 Invalid 类型:
1 2 fmt.Printf("%v\n" , reflect.TypeOf(s).Kind()) fmt.Printf("%v\n" , reflect.TypeOf(me.GetIntro).Kind())
Value 使用 reflect.ValueOf(v)
获取到变量的值,它是只读的。若想修改 v 的值,需使用 reflect.ValueOf(&v)
获取、修改静态类型的变量值 使用 Interface()
能将变量的值以 interface{}
类型返回,再强制转换即可获取变量的实际值:
可使用 Elem()
来获取它们指向或存储的元素值:
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { str := "old" strVal := reflect.ValueOf(str) fmt.Println(strVal.Interface()) strPtrVal := reflect.ValueOf(&str) strPtrVal.Elem().SetString("new" ) fmt.Println(str) strPtrVal.Elem().Set(reflect.ValueOf("newNew" )) fmt.Println(str) }
可以看出, reflect 的大量方法使用不当会直接 panic,需小心使用。
获取、修改未知的 struct 类型字段的值、调用方法 如果变量是 struct,可使用 NumField()
返回字段数量,再遍历获取、修改字段的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 type User struct { FirstName string `tag_name:"front"` LastName string `tag_name:"back"` Age int `tag_name:"young"` } func main () { u := User{"wu" , "Yin" , 20 } represent(u) uType := reflect.TypeOf(u) newU := reflect.New(uType) newU.Elem().Field(0 ).SetString("Frank" ) newU.Elem().Field(1 ).SetString("Underwood" ) newU.Elem().Field(2 ).SetInt(50 ) newUser := newU.Elem().Interface().(User) fmt.Printf("%+v" , newUser) } func represent (i interface {}) { t := reflect.TypeOf(i) v := reflect.ValueOf(i) for i := 0 ; i < t.NumField(); i++ { fieldVal := v.Field(i) fieldType := t.Field(i) fieldTag := fieldType.Tag fmt.Printf("Field Name: %s\t Field Value: %v \tTag Value: %s\t\n" , fieldType.Name, fieldVal, fieldTag.Get("tag_name" )) } for i := 0 ; i < t.NumMethod(); i++ { m := t.Method(i) fmt.Printf("%s :%v\n" , m.Name, m.Type) } m := v.MethodByName("Intro" ) args := []reflect.Value{reflect.ValueOf("Beijing" ), reflect.ValueOf("Xian" )} m.Call(args) } func (u User) Intro (workLoc string , studyLoc string ) { fmt.Printf("My name is %s%s, age %d, working in %s and study in %s\n" , u.FirstName, u.LastName, u.Age, workLoc, studyLoc) }
输出:
1 2 3 4 5 6 Field Name: FirstName Field Value: wu Tag Value: front Field Name: LastName Field Value: Yin Tag Value: back Field Name: Age Field Value: 20 Tag Value: young Intro :func(main.User, string, string) My name is wuYin, age 20, working in Beijing and study in Xian {FirstName:Frank LastName:Underwood Age:50}
如果 struct 组合嵌套了,可以使用递归来处理。
Make 系列方法 除使用 make()
来为 slice、map 和 channel 分配空间,还能用反射包中的 Make 系列方法:
1 2 3 4 func MakeSlice (typ Type, len , cap int ) Value {}func MakeMap (typ Type) Value {}func MakeMapWithSize (typ Type, n int ) Value {}func MakeChan (typ Type, buffer int ) Value {}
拿 slice 和 map 举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func main () { intSlice := make ([]int , 0 ) strIntMap := make (map [string ]int ) sliceType := reflect.TypeOf(intSlice) mapType := reflect.TypeOf(strIntMap) reflectSlice := reflect.MakeSlice(sliceType, 0 , 0 ) reflectMap := reflect.MakeMap(mapType) i := 233 iVal := reflect.ValueOf(i) reflectSlice = reflect.Append(reflectSlice, iVal) originSlice := reflectSlice.Interface().([]int ) fmt.Println(originSlice) s := "str" sVal := reflect.ValueOf(s) reflectMap.SetMapIndex(sVal, iVal) originMap := reflectMap.Interface().(map [string ]int ) fmt.Println(originMap) }
reflect 还能创建函数:
1 func MakeFunc (typ Type, fn func (args []Value) (results []Value) ) Value {}
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { newFunc := MyMakeFunc(beYounger).(func (i int ) int ) // 传入函数类型 res := newFunc(20 ) print (res) } func MyMakeFunc (fun interface {}) interface {} { funcVal := reflect.ValueOf(fun) funcType := funcVal.Type() newFun := reflect.MakeFunc(funcType, func (in []reflect.Value) []reflect .Value { println ("创建的新函数被调用" ) return funcVal.Call(in) }) return newFun.Interface() } func beYounger (age int ) int { return age - 10 }
输出:
总结 Go 的反射经常操作 interface{}
类型的变量,在程序运行前是不知道这个变量的具体类型和值的,反射就提供了这种在运行时检测和操作 interface{}
类型变量的机制,比如经常用来调试的 fmt.Printf("%v", v)
函数,内部实现就使用了大量的反射。
初学容易混淆 reflect.Type
和 reflect.Value
,尤其是
1 2 3 4 5 没有方法 // 有类型不代表有值 ------> reflect.Type reflect.Value <------ .Type() // 有值肯定有类型
另外,在使用反射时,一定要熟悉方法的参数类型等,否则容易造成 panic