我们在开发的时候经常会遇到字符串跟结构体之间的转换,比如在调用 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
 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 main
import (  "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
       复制代码
 
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 方法获得。
要修改反射的对象,该值必须可设置,也就是可寻址,参考上节课修改变量的值那一节的内容理解。
评论