• 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 
    ", val)
        val.Elem().SetInt(110)
        fmt.Printf("val = %v 
    ", 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
    ", num) //2
        // 变量结构体的所有字段
        for i := 0; i < num; i++ {
            fmt.Printf("Field %d: 值为=%v
    ", i, val.Field(i))
            // 获取到 struct 标签, 注意需要通过 reflect.Type 来获取 tag 标签的值
            tagVal := typ.Field(i).Tag.Get("json")
            // 如果该字段于 tag 标签就显示,否则就不显示
            if tagVal!="" {
                fmt.Printf("Field %d: tag 为=%v
    ", i, tagVal)
            }
        }
    
        // 获取到该结构体有多少个方法
        numOfMethod := val.NumMethod()
        fmt.Printf("struct has %d methods
    ", 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、反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

  • 相关阅读:
    js 面试的坑:变量提升
    meta 标签大全
    一个极为简单的requirejs实现
    AMD 的 CommonJS wrapping
    浅解析js中的对象
    javascript运动系列第二篇——变速运动
    开发汉澳即时通信网,2006年上线,QQ死期到了
    SpringMVC中的异步提交表单
    HDU 3698 DP+线段树
    黑马程序猿_反射、内省、泛型
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/14658120.html
Copyright © 2020-2023  润新知