咱们在开发的时候常常会遇到字符串跟构造体之间的转换,比方在调用 API 时,须要将 JSON 字符串转成 struct 构造体。具体如何进行转换,就须要用到反射了。
反射
对于反射,之前的文章曾经有所介绍,传送门:《断言、反射的了解与应用!》,此处咱们再深刻解说下反射。
reflect.Value 和 reflect.Type
反射有两种类型 reflect.Value
和 reflect.Type
,别离示意变量的值和类型,并且提供了两个函数 reflect.ValueOf
和 reflect.TypeOf
别离获取任意对象的 reflect.Value
和 reflect.Type
。
reflect.Value
reflect.Value
能够通过函数 reflect.ValueOf
取得。reflect.Value
被定义为一个 struct 构造体:
type Value struct { typ *rtype ptr unsafe.Pointer flag}
能够看到,这个构造体中的字段都是公有的,咱们只能应用 reflect.Value 的办法,它的办法有:
//针对具体类型的系列办法//以下是用于获取对应的值BoolBytesComplexFloatIntStringUintCanSet //是否能够批改对应的值//以下是用于批改对应的值SetSetBoolSetBytesSetComplexSetFloatSetIntSetStringElem //获取指针指向的值,个别用于批改对应的值//以下Field系列办法用于获取struct类型中的字段FieldFieldByIndexFieldByNameFieldByNameFuncInterface //获取对应的原始类型IsNil //值是否为nilIsZero //值是否是零值Kind //获取对应的类型类别,比方Array、Slice、Map等//获取对应的办法MethodMethodByNameNumField //获取struct类型中字段的数量NumMethod//类型上办法集的数量Type//获取对应的reflect.Type
办法分为三类:
- 获取和批改对应的值
- struct 类型的字段无关,用于获取对应的字段
- 类型上的办法集无关,用于获取对应的办法
任意类型的对象 与 reflect.Value 互转:
func main() { i := 5 //int to reflect.Value iv:=reflect.ValueOf(i) //reflect.Value to int i1 := iv.Interface().(int) fmt.Println(i1)}
批改对应的值
func main() { i := 5 ipv := reflect.ValueOf(&i) ipv.Elem().SetInt(6) fmt.Println(i)}
- 示例中咱们通过反射批改了一个变量
- reflect.ValueOf 函数返回的是一份值的拷贝,所以咱们要传入变量的指针才能够
- 因为传递的是一个指针,所以须要调用 Elem 办法找到这个指针指向的值,这样能力批改
- 要批改一个变量的值,关键点:传递指针(可寻址),通过 Elem 办法获取指向的值,才能够保障值能够被批改,reflect.Value 为咱们提供了 CanSet 办法判断是否能够批改该变量
批改 struct 构造体字段的值
func main() { p := person{Name: "微客鸟窝",Age: 18} pv:=reflect.ValueOf(&p) pv.Elem().Field(0).SetString("无尘") fmt.Println(p)}type person struct { Name string Age int}
步骤总结:
- 传递一个 struct 构造体的指针,获取对应的 reflect.Value;
- 通过 Elem 办法获取指针指向的值;
- 通过 Field 办法获取要批改的字段;
通过 Set 系列办法批改成对应的值。
通过反射批改一个值的规定:
- 可被寻址,艰深地讲就是要向 reflect.ValueOf 函数传递一个指针作为参数。
- 如果要批改 struct 构造体字段值的话,该字段须要是可导出的,而不是公有的,也就是该字段的首字母为大写。
- 记得应用 Elem 办法取得指针指向的值,这样能力调用 Set 系列办法进行批改。
获取对应的底层类型
因为咱们能够通过 type 关键字来自定义一些新的类型,而底层类型就是一些根底类型。比方下面示例中的 p 变量类型为 person,底层类型为 struct 构造体类型。
func main() { p := person{Name: "微客鸟窝",Age: 18} pv:=reflect.ValueOf(&p) fmt.Println(pv.Kind()) pv:=reflect.ValueOf(p) fmt.Println(pv.Kind())}//运行后果:ptrstruct
Kind 办法返回一个 Kind 类型的值,它是一个常量,从源码看下定义的 Kind 常量列表,含了 Go 语言的所有底层类型:
type Kind uintconst ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer)
reflect.Type
- reflect.Type 是一个接口,而不是一个构造体,所以也只能应用它的办法。
reflect.Type 接口罕用的办法
type Type interface { Implements(u Type) bool //办法用于判断是否实现了接口 u; AssignableTo(u Type) bool //办法用于判断是否能够赋值给类型 u,其实就是是否能够应用 =,即赋值运算符; ConvertibleTo(u Type) bool //办法用于判断是否能够转换成类型 u,其实就是是否能够进行类型转换; Comparable() bool //办法用于判断该类型是否是可比拟的,其实就是是否能够应用关系运算符进行比拟。 //以下这些办法和Value构造体的性能雷同 Kind() Kind Method(int) Method MethodByName(string) (Method, bool) NumMethod() int Elem() Type Field(i int) StructField FieldByIndex(index []int) StructField FieldByName(name string) (StructField, bool) FieldByNameFunc(match func(string) bool) (StructField, bool) NumField() int}
遍历构造体的字段和办法
package mainimport ( "fmt" "reflect")func main() { p := person{Name: "微客鸟窝", Age: 18} pt := reflect.TypeOf(p) //遍历person的字段 for i := 0; i < pt.NumField(); i++ { fmt.Println("字段:", pt.Field(i).Name) } //遍历person的办法 for i := 0; i < pt.NumMethod(); i++ { fmt.Println("办法:", pt.Method(i).Name) }}type person struct { Name string Age int}func (p person) String() string{ return fmt.Sprintf("Name is %s,Age is %d",p.Name,p.Age)}
运行后果:
字段: Name字段: Age办法: String
是否实现某接口
//....func main() { p := person{Name: "微客鸟窝", Age: 20} pt := reflect.TypeOf(p) stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem() writerType := reflect.TypeOf((*io.Writer)(nil)).Elem() fmt.Println("是否实现了fmt.Stringer:", pt.Implements(stringerType)) fmt.Println("是否实现了io.Writer:", pt.Implements(writerType))}//...
运行后果:
是否实现了fmt.Stringer: true是否实现了io.Writer: false
JSON 和 Struct 互转
咱们能够通过规范库中的 json 包进行JSON 和 Struct 互转:
//......func main() { p := person{Name: "微客鸟窝", Age: 18} //struct to json jsonB, err := json.Marshal(p) if err == nil { fmt.Println(string(jsonB)) } //json to struct respJSON := "{\"Name\":\"无尘\",\"Age\":18}" json.Unmarshal([]byte(respJSON), &p) fmt.Println(p)}//......
运行后果:
{"Name":"微客鸟窝","Age":18}Name is 无尘,Age is 18
- 通过 json.Marshal 函数,能够把 struct 转为 JSON 字符串。
- 通过 json.Unmarshal 函数,能够把 JSON 字符串转为 struct。
Struct Tag
struct tag 是一个增加在 struct 字段上的标记,应用它进行辅助。要想取得字段上的 tag,就要先反射取得对应的字段,能够通过 Field 办法做到。该办法返回一个 StructField 构造体,它有一个字段是 Tag,存有字段的所有 tag。
示例:
//遍历person字段中key为json、bson的tagfor i:=0;i<pt.NumField();i++{ sf:=pt.Field(i) fmt.Printf("字段%s上,json tag为%s\n",sf.Name,sf.Tag.Get("json")) fmt.Printf("字段%s上,bson tag为%s\n",sf.Name,sf.Tag.Get("bson"))}type person struct { Name string `json:"name" bson:"b_name"` Age int `json:"age" bson:"b_name"`}
运行后果:
字段Name上,key为json的tag为name字段Name上,key为bson的tag为b_name字段Age上,key为json的tag为age字段Age上,key为bson的tag为b_name
反射定律
Go 语言的作者在博客上总结了反射的三大定律:
- 任何接口值 interface{} 都能够反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 取得。
- 反射对象也能够还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 构造体的 Interface 办法取得。
- 要批改反射的对象,该值必须可设置,也就是可寻址,参考上节课批改变量的值那一节的内容了解。