go-反射

反射的基本介绍

1、反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)

2、如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)

3、通过反射,可以修改变量的值,可以调用关联的方法。

4、使用反射,需要 import (“reflect”)

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

反射的应用场景

反射常见的应用场景有以下两种:

  1、不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法进行反射.

  2、对结构体序列化时,如果结构体有指定tag,也会使用到反射生成对应的字符串

package main

import (
    "encoding/json"
    "fmt"
)

type Monster struct {
    Name string `json:"monsterName"`
    Age  int    `json:"monsterAge"`
}

func main() {
    m := Monster{
        Name: "bingle",
        Age:  18,
    }
    data, _ := json.Marshal(m)
    fmt.Println("json result : ", string(data))
}

反射重要的函数和概念

1、reflect.TypeOf(变量名),获取变量的类型,返回 reflect.Type 类型

2、reflect.ValueOf(变量名),获取变量的值,返回reflect.Value 类型,reflect.Value 是一个结构体类型。通过reflect.Value ,可以获取到该变量的很多信息。

3、变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。

反射的注意事项和细节

1、reflect.Value.Kind,获取变量的类别,返回的是一个常量

在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、Type 和 Kind 的区别

  Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的.

  比如:var num int = 10 num 的 Type 是 int , Kind 也是 int

  比如:var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct

3、通过反射,可以让变量在 interface{} 和 Reflect.Value 之间进行转换,

4、使用反射的方式来获取变量的值(并发挥对应的类型),要求数据类型匹配

  比如,x 是 int ,那么就应该使用 reflect.Value(x).Int(),而不能使用其他的,否则会报 panic

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

注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法

package main

import (
    "fmt"
    "reflect"
)

func testInt(b interface{}) {
    val := reflect.ValueOf(b)
    fmt.Printf("val type = %T \n", val)
    val.Elem().SetInt(110)
    fmt.Printf("val = %v \n", val)
}

func main() {
    var num int = 20
    testInt(&num)
    fmt.Println("num = ", num)
}

上段代码,执行结果如下:

反射的实践

1、使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值

package main

import (
    "fmt"
    "reflect"
)

//定义了一个 Person 结构体
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"monster_age"`
}

//方法,返回两个数的和
func (p Person) GetSum(n1, n2 int) int {
    return n1 + n2
}

//方法, 接收两个值,给 p 赋值
func (p Person) Set(name string, age int) {
    p.Name = name
    p.Age = age
}

//方法,显示 p 的值
func (p Person) Print() {
    fmt.Println("---start~----")
    fmt.Println(p)
    fmt.Println("---end~----")
}

func TestStruct(a interface{}) {
    // 获取 reflect.Type 类型
    typ := reflect.TypeOf(a)
    // 获取 reflect.Value 类型
    val := reflect.ValueOf(a)
    // 获取到 a 对应的类别
    kind := val.Kind()

    // 如果传入的不是 struct,就退出
    if kind != reflect.Struct {
        fmt.Println("expect struct")
        return
    }
    // 获取到该结构体有几个字段
    num := val.NumField()
    fmt.Printf("struct has %d fields\n", num) //2
    // 变量结构体的所有字段
    for i := 0; i < num; i++ {
        fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))
        // 获取到 struct 标签, 注意需要通过 reflect.Type 来获取 tag 标签的值
        tagVal := typ.Field(i).Tag.Get("json")
        // 如果该字段于 tag 标签就显示,否则就不显示
        if tagVal!="" {
            fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)
        }
    }

    // 获取到该结构体有多少个方法
    numOfMethod := val.NumMethod()
    fmt.Printf("struct has %d methods\n", numOfMethod)
    // var params []reflect.Value
    // 方法的排序默认是按照 函数名的排序(ASCII 码)
    val.Method(1).Call(nil) // 获取到第二个方法。调用它
    // 调用结构体的第 1 个方法 Method(0)
    var params []reflect.Value // 声明了 []reflect.Value
    params = append(params,reflect.ValueOf(10))
    params = append(params,reflect.ValueOf(40))
    res := val.Method(0).Call(params) // 传入的参数是 []reflect.Value, 返回[]reflect.Value
    fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/
}

func main() {
    // 创建了一个 Person 实例
    var person Person =Person{
        Name: "bingle",
        Age: 18,
    }
    // 将 Person 实例传递给 TestStruct 函数
    TestStruct(person)
}

ValueOf

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

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

isNil()和isValid()

isNil()

func (v Value) IsNil() bool

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

isValid()

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("bingle")).IsValid())
}

与结构体相关的方法

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

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

反射是把双刃剑

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

  1、基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。

  2、大量使用反射的代码通常难以理解。

  3、反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

 

posted @ 2021-04-14 15:25  冰乐  阅读(157)  评论(0编辑  收藏  举报