第15篇:反射

一、什么是反射

官方说法

在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。

通俗一点就是:可以知道本数据的原始数据类型和数据内容,方法等、并且可以进行一定操作。

二、为什么使用反射

我们知道,Go语言中的变量是分为两部分的:类型信息,即预先定义好的元信息;值信息,程序运行过程中可动态变化的。在之前我们介绍了接口。 空接口可以存储任意类型的变量,那我们如何知道这个空接口保存的数据是什么呢? 反射就是在运行时动态的获取一个变量的类型信息和值信息。

  • 我们通过接口或者其他的方式接收到了类型不固定的数据的时候,需要写太多的swatch case 断言代码 ,此时代码不灵活且通用性差,反射这时候就可以无视类型,改变原数据结构中的数据。
  • 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go程序在运行期使用reflect包访问程序的反射信息。

三、reflect包

在Go语言的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,并且reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的Value和Type。

1. TypeOf:获取基本类型

在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。

  • reflect.TypeOf(),获取变量的类型,返回reflect.Type类型
  • reflect.Type.name,获取变量类型的名字
  • reflect.Type.kind,获取变量类型的种类

示例:

package main

import (
    "fmt"
    "reflect"
)

func reflectType(x interface{}) {
    v := reflect.TypeOf(x)
    fmt.Printf("type:%v\n", v)
}
func main() {
    var a float32 = 3.14
    reflectType(a) // type:float32
    var b int64 = 100
    reflectType(b) // type:int64
}

type name和type kind

在反射中关于类型还划分为两种:类型(Type)种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了一个指针类型、一个自定义int64、一个rune和两个结构体类型,通过反射查看它们的类型和种类。

package main

import (
    "fmt"
    "reflect"
)

type myInt int64

func reflectType(x interface{}) {
    t := reflect.TypeOf(x)
    fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}

func main() {
    var a *float32 // 指针
    var b myInt    // 自定义类型
    var c rune     // 类型别名
    reflectType(a) // type: kind:ptr
    reflectType(b) // type:myInt kind:int64
    reflectType(c) // type:int32 kind:int32

    type person struct {
        name string
        age  int
    }
    type book struct{ title string }
    var d = person{
        name: "小王子",
        age:  18,
    }
    var e = book{title: "《跟小王子学Go语言》"}
    reflectType(d) // type:person kind:struct
    reflectType(e)
}

Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回

reflect包中定义的Kind类型如下:

type Kind uint

const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

2. ValueOf: 获取变量的值

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

  • reflect.ValueOf(),获取变量的值,返回reflect.Value类型
  • reflect.Value.Kind(),获取变量的类别,返回一个常量
  • reflect.Value.Interface(),转换成interface{}类型
  • reflect.Value.Elem().Kind(),获取变量指针对应的地址,返回其类型
  • reflect.Value.Elem().Setxx(),获取变量指针对应的地址,修改变量值

reflect.Value类型提供的获取原始值的方法如下:

方法 说明
Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回
通过反射获得值
func reflectValue(x interface{}) {
    v := reflect.ValueOf(x)
    k := v.Kind()
    switch k {
    case reflect.Int64:
        // v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
        fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
    case reflect.Float32:
        // v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
        fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
    case reflect.Float64:
        // v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
        fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
    }
}
func main() {
    var a float32 = 3.14
    var b int64 = 100
    reflectValue(a) // type is float32, value is 3.140000
    reflectValue(b) // type is int64, value is 100
    // 将int类型的原始值转换为reflect.Value类型
    c := reflect.ValueOf(10)
    fmt.Printf("type c :%T\n", c) // type c :reflect.Value
}
通过反射修改变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。

package main

import (
    "fmt"
    "reflect"
)

func reflectSetValue1(x interface{}) {
    v := reflect.ValueOf(x)
    if v.Kind() == reflect.Int64 {
        v.SetInt(200) //修改的是副本,reflect包会引发panic
    }
}
func reflectSetValue2(x interface{}) {
    v := reflect.ValueOf(x)
    // 反射中使用 Elem()方法获取指针对应的值
    if v.Elem().Kind() == reflect.Int64 {
        v.Elem().SetInt(200)
    }
}
func main() {
    var a int64 = 100
    // reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value
    reflectSetValue2(&a)
    fmt.Println(a)
}
isNil()isValid()
func (v Value) IsNil() bool

IsNil()报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、字典、指针、切片之一;否则IsNil函数会导致panic。

func (v Value) IsValid() bool

IsValid()返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。

举个例子:IsNil()常被用于判断指针是否为空;IsValid()常被用于判定返回值是否有效。

func main() {
    // *int类型空指针
    var a *int
    fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
    // nil值
    fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
    // 实例化一个匿名结构体
    b := struct{}{}
    // 尝试从结构体中查找"abc"字段
    fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
    // 尝试从结构体中查找"abc"方法
    fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
    // map
    c := map[string]int{}
    // 尝试从map中查找一个不存在的键
    fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("小王子")).IsValid())
}

3. 结构体反射

与结构体相关的方法

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()Field()方法获得结构体成员的详细信息。

reflect.Type中与获取结构体成员相关的的方法如下表所示。

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据传入的匹配函数匹配需要的字段。
NumMethod() int 返回该类型的方法集中方法的数目
Method(int) Method 返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool) 根据方法名返回该类型方法集中的方法
StructField类型

StructField类型用来描述结构体中的一个字段的信息。

StructField的定义如下:

type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}
反射获取结构体
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name  string
    Age   int
    Score float32
}

func test(b interface{}) {
    t := reflect.TypeOf(b)
    fmt.Println(t)

    v := reflect.ValueOf(b)
    fmt.Println(v)

    k := v.Kind()
    fmt.Println(k)

    iv := v.Interface()
    fmt.Println(iv)

    stu, ok := iv.(Student)
    if ok {
        fmt.Printf("%v %T\n", stu, stu)
    }
}

func main() {
    var a Student = Student{
        Name:  "stu01",
        Age:   18,
        Score: 92,
    }
    test(a)
}

输出

main.Student
{stu01 18 92}
struct
{stu01 18 92}
{stu01 18 92} main.Student
反射获取结构体字段

当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。

type student struct {
    Name  string `json:"name"`
    Score int    `json:"score"`
}

func main() {
    stu1 := student{
        Name:  "小王子",
        Score: 90,
    }

    t := reflect.TypeOf(stu1)
    fmt.Println(t.Name(), t.Kind()) // student struct
    // 通过for循环遍历结构体的所有字段信息
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
    }

    // 通过字段名获取指定结构体字段信息
    if scoreField, ok := t.FieldByName("Score"); ok {
        fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
    }
}
匿名或嵌入字段的反射
package main

import (
    "fmt"
    "reflect"
)

//反射refection
//反射使用TypeOf和ValueOf函数从接口中获取目标对象信息
//反射会将匿名字段作为独立字段

type A struct {
    id   int
    name string
    age  int
}

type B struct {
    A
    height int
}

func (a A) Hello() {
    fmt.Println("A")
}

func info(inf interface{}) {
    t := reflect.TypeOf(inf)
    fmt.Println(t.Name())
    v := reflect.ValueOf(inf)

    //遍历出结构中的字段名,字段类型和值
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i)
        fmt.Println(f.Name, f.Type, val)
    }

    //遍历出方法,注意这里只遍历出公开方法
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Println(m.Name, m.Type)
    }
}

func main() {
    a := A{1, "test", 25}
    a.Hello()
    info(a)
    b := B{A: A{id: 2, name: "test2", age: 28}, height: 172}
    t := reflect.TypeOf(b)

    //获得匿名字段信息
    fmt.Printf("%#v\n", t.Field(0))
    //获得匿名字段结构中的字段
    //匿名字段A的索引相对B是0,id相对于A的索引是0
    fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 0}))
    //name相对于A的索引是1
    fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))
}

输出

A
A
id int 1
name string test
age int 25
Hello func(main.A)
reflect.StructField{Name:"A", PkgPath:"", Type:(*reflect.rtype)(0xa77180), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"id", PkgPath:"main", Type:(*reflect.rtype)(0xa65ba0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"name", PkgPath:"main", Type:(*reflect.rtype)(0xa662a0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
反射调用结构体方法

示例1

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name  string
    Age   int
    Score float32
}

func (s Student) Print() {
    fmt.Println(s)
}

func (s Student) Set(name string, age int, score float32) {
    s.Age = age
    s.Name = name
    s.Score = score
}

func TestStruct(a interface{}) {
    val := reflect.ValueOf(a)
    kd := val.Kind()

    fmt.Println(val, kd)
    if kd != reflect.Struct {
        fmt.Println("expect struct")
        return
    }
    //获取字段数量
    fields := val.NumField()
    fmt.Printf("struct has %d field\n", fields)
    //获取字段的类型
    for i := 0; i < fields; i++ {
        fmt.Printf("%d %v\n", i, val.Field(i).Kind())
    }
    //获取方法数量
    methods := val.NumMethod()
    fmt.Printf("struct has %d methods\n", methods)

    //反射调用的Print方法
    var params []reflect.Value
    val.Method(0).Call(params)

}

func main() {
    var a Student = Student{
        Name:  "stu01",
        Age:   18,
        Score: 92.8,
    }
    TestStruct(a)
    // fmt.Println(a)
}

输出

{stu01 18 92.8} struct
struct has 3 field
0 string
1 int
2 float32
struct has 2 methods
{stu01 18 92.8}

示例2

// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s student) Study() string {
    msg := "好好学习,天天向上。"
    fmt.Println(msg)
    return msg
}

func (s student) Sleep() string {
    msg := "好好睡觉,快快长大。"
    fmt.Println(msg)
    return msg
}

func printMethod(x interface{}) {
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)

    fmt.Println(t.NumMethod())
    for i := 0; i < v.NumMethod(); i++ {
        methodType := v.Method(i).Type()
        fmt.Printf("method name:%s\n", t.Method(i).Name)
        fmt.Printf("method:%s\n", methodType)
        // 通过反射调用方法传递的参数必须是 []reflect.Value 类型
        var args = []reflect.Value{}
        v.Method(i).Call(args)
    }
}
Elem反射操作结构体
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name  string
    Age   int
    Score float32
}

func (s Student) Print() {
    fmt.Println(s)
}

func (s Student) Set(name string, age int, score float32) {
    s.Age = age
    s.Name = name
    s.Score = score
}

func TestStruct(a interface{}) {
    val := reflect.ValueOf(a)
    kd := val.Kind()

    fmt.Println(val, kd)
    if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
        fmt.Println("expect struct")
        return
    }
    //获取字段数量
    fields := val.Elem().NumField()
    fmt.Printf("struct has %d field\n", fields)
    //获取字段的类型
    for i := 0; i < fields; i++ {
        fmt.Printf("%d %v\n", i, val.Elem().Field(i).Kind())
    }
    //获取方法数量
    methods := val.NumMethod()
    fmt.Printf("struct has %d methods\n", methods)

    //反射调用的Print方法
    var params []reflect.Value
    val.Elem().Method(0).Call(params)
}

func main() {
    var a Student = Student{
        Name:  "stu01",
        Age:   18,
        Score: 92.8,
    }
    TestStruct(&a)
    // fmt.Println(a)
}

输出结果:

&{stu01 18 92.8} ptr
struct has 3 field
0 string
1 int
2 float32
struct has 2 methods
{stu01 18 92.8}
Elem反射获取tag
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name  string `json:"stu_name"`
    Age   int
    Score float32
}

func TestStruct(a interface{}) {
    typ := reflect.TypeOf(a)

    tag := typ.Elem().Field(0).Tag.Get("json")
    fmt.Printf("Tag:%s\n", tag)
}

func main() {
    var a Student = Student{
        Name:  "stu01",
        Age:   18,
        Score: 92.8,
    }
    TestStruct(&a)
}

输出结果:

Tag:stu_name

获取 tag(一)

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type User struct {
        Name   string "user name"
        Passwd string `user passsword`
    }
    u := &User{
        Name:   "Murphy",
        Passwd: "123456",
    }
    s := reflect.TypeOf(u).Elem()
    for i := 0; i < s.NumField(); i++ {
        fmt.Println(s.Field(i).Tag)
    }
}

获取 tag(二)

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type User struct {
        Name string `json:"user_name" name:"user name"`
    }
    u := User{
        Name: "Murphy",
    }
    f := reflect.TypeOf(u).Field(0)
    fmt.Println(f.Tag.Get("json"))
    fmt.Println(f.Tag.Get("name"))
}
应用练习
  • 1.定义一个结构体
  • 2.给结构体赋值
  • 3.用反射获取结构体的 下标、结构体名称、类型、值
  • 4.改变结构体的值

参考代码

package main

import (
    "fmt"
    "reflect"
)

type T struct {
    A int
    B string
}

func main() {
    t := T{23, "skidoo"}
    s := reflect.ValueOf(&t).Elem()
    typeOfT := s.Type()
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
    }
    s.Field(0).SetInt(77)
    s.Field(1).SetString("Sunset Strip")
    fmt.Println("t is now", t)
}

输出结果:

0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}

反射是把双刃剑

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
  2. 大量使用反射的代码通常难以理解。
  3. 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

文章作者: 张亚飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张亚飞 !
评论
 上一篇
第16篇:JSON序列化 第16篇:JSON序列化
JavaScript对象表示法(JSON)是一种用于发送和接收结构化信息的标准协议。在类似的协议中,JSON并不是唯一的一个标准协议。 XML、ASN.1和Google的Protocol Buffers都是类似的协议,并且有各自的特色,但是由于简洁性、可读性和流行程度等原因,JSON是应用最广泛的一个。
2020-12-31
下一篇 
第14篇:标准库中的接口 第14篇:标准库中的接口
一、sort.Interface接口排序操作和字符串格式化一样是很多程序经常使用的操作。尽管一个最短的快排程序只要15行就可以搞定,但是一个健壮的实现需要更多的代码,并且我们不希望每次我们需要的时候都重写或者拷贝这些代码。 幸运的是,sor
2020-12-31
  目录