咱们在开发的时候常常会遇到字符串跟构造体之间的转换,比方在调用 API 时,须要将 JSON 字符串转成 struct 构造体。具体如何进行转换,就须要用到反射了。
反射对于反射,之前的文章曾经有所介绍,传送门:《断言、反射的了解与应用!》,此处咱们再深刻解说下反射。
reflect.Value 和 reflect.Type
reflect.Valuereflect.Typereflect.ValueOfreflect.TypeOfreflect.Valuereflect.Typereflect.Value
reflect.Valuereflect.ValueOfreflect.Valuetype Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}能够看到,这个构造体中的字段都是公有的,咱们只能应用 reflect.Value 的办法,它的办法有:
//针对具体类型的系列办法
//以下是用于获取对应的值
Bool
Bytes
Complex
Float
Int
String
Uint
CanSet //是否能够批改对应的值
//以下是用于批改对应的值
Set
SetBool
SetBytes
SetComplex
SetFloat
SetInt
SetString
Elem //获取指针指向的值,个别用于批改对应的值
//以下Field系列办法用于获取struct类型中的字段
Field
FieldByIndex
FieldByName
FieldByNameFunc
Interface //获取对应的原始类型
IsNil //值是否为nil
IsZero //值是否是零值
Kind //获取对应的类型类别,比方Array、Slice、Map等
//获取对应的办法
Method
MethodByName
NumField //获取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())
}
//运行后果:
ptr
structKind 办法返回一个 Kind 类型的值,它是一个常量,从源码看下定义的 Kind 常量列表,含了 Go 语言的所有底层类型:
type Kind uint
const (
   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 包进行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的tag
for 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_nameGo 语言的作者在博客上总结了反射的三大定律:
- 任何接口值 interface{} 都能够反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 取得。
- 反射对象也能够还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 构造体的 Interface 办法取得。
- 要批改反射的对象,该值必须可设置,也就是可寻址,参考上节课批改变量的值那一节的内容了解。
