• 『GoLang』反射


    方法和类型的反射

    反射是应用程序检查其所拥有的结构,尤其是类型的一种能。每种语言的反射模型都不同,并且有些语言根本不支持反射。Go语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,即可从运行时态的示例对象反求其编码阶段的定义,标准库中reflect包提供了相关的功能。在reflect包中,通过reflect.TypeOf()reflect.ValueOf()分别从类型、值的角度来描述一个Go对象。

    func TypeOf(i interface{}) Type
    type Type interface 
    
    func ValueOf(i interface{}) Value
    type Value struct 
    

    在Go语言的实现中,一个interface类型的变量存储了2个信息, 一个<值,类型>对,<value,type> :

    (value, type)
    

    value是实际变量值,type是实际变量的类型。两个简单的函数,reflect.TypeOfreflect.ValueOf,返回被检查对象的类型和值。

    例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64reflect.ValueOf(x) 返回 3.4。实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

    func TypeOf(i interface{}) Type
    func ValueOf(i interface{}) Value
    

    reflect.Typereflect.Value 都有许多方法用于检查和操作它们。

    Type主要有:

    • Kind() 将返回一个常量,表示具体类型的底层类型
    • Elem()方法返回指针、数组、切片、字典、通道的基类型,这个方法要慎用,如果用在其他类型上面会出现panic

    Value主要有:

    • Type()将返回具体类型所对应的 reflect.Type(静态类型)
    • Kind() 将返回一个常量,表示具体类型的底层类型
    • Elem()返回接口所包含的值,或者,指针指向的值

    反射可以在运行时检查类型和变量,例如它的大小、方法和动态的调用这些方法。这对于没有源代码的包尤其有用。

    由于反射是一个强大的工具,但反射对性能有一定的影响,除非有必要,否则应当避免使用或小心使用。下面代码针对int、数组以及结构体分别使用反射机制,其中的差异请看注释。

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type Student struct {
        name string
    }
    
    func main() {
    
        var a int = 50
        v := reflect.ValueOf(a) // 返回Value类型对象,值为50
        t := reflect.TypeOf(a)  // 返回Type类型对象,值为int
        fmt.Println(v, t, v.Type(), t.Kind())  // 50 int int int
    
        var b [5]int = [5]int{5, 6, 7, 8}
        fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int
    
        var Pupil Student
        p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
    
        fmt.Println(p.Type()) // 输出:main.Student
        fmt.Println(p.Kind()) // 输出:struct
    
    }
    

    在Go语言中,类型包括 static typeconcrete type。简单说 static type 是你在编码是看见的类型(如intstring),concrete type 是实际具体的类型,runtime 系统看见的类型

    Type()返回的是静态类型,而 Kind() 返回的是具体类型。上面代码中,在 int,数组以及结构体三种类型情况中,可以看到 kind()type()返回值的差异。

    通过反射可以修改原对象

    • CanAddr()方法:判断它是否可被取地址

    • CanSet()方法:判断它是否可被取地址并可被修改
      是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性

    通过一个settableValue反射对象来访问、修改其对应的变量值:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type Student struct {
        name string
        Age  int
    }
    
    func main() {
    
        var a int = 50
        v := reflect.ValueOf(a) // 返回Value类型对象,值为50
        t := reflect.TypeOf(a)  // 返回Type类型对象,值为int
        fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())  // 50 int int int 50
        seta := reflect.ValueOf(&a).Elem() // 这样才能让seta保存a的值
        fmt.Println(seta, seta.CanSet())  // 50 true
        seta.SetInt(1000)
        fmt.Println(seta)  // 1000
    
        var b [5]int = [5]int{5, 6, 7, 8}
        fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem())  // [5]int array int
    
        var Pupil Student = Student{"joke", 18}
        p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
    
        fmt.Println(p.Type()) // 输出:main.Student
        fmt.Println(p.Kind()) // 输出:struct
    
        setStudent := reflect.ValueOf(&Pupil).Elem()
        //setStudent.Field(0).SetString("Mike") // 未导出字段,不能修改,panic会发生
        setStudent.Field(1).SetInt(19)
        fmt.Println(setStudent)  // {joke 19}
    }
    

    虽然反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,但不能修改这些未导出的成员。因为一个结构体中只有被导出的字段才是可修改的(就是对外可见的才可以修改)

    v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)。然后这个是返回的是指针,也是不可修改的,需要再调用 Elem() 函数,即setx = reflect.ValueOf(&x).Elem(),假如 xint 类型的,然后可以setx.SetInt(111)这样修改了

    在结构体中有tag标签,通过反射可获取结构体成员变量的tag信息。

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type Student struct {
        name string
        Age  int      `json:"years"`
    }
    
    func main() {
        var Pupil Student = Student{"joke", 18}
        setStudent := reflect.ValueOf(&Pupil).Elem()
    
        sSAge, _ := setStudent.Type().FieldByName("Age")
        fmt.Println(sSAge.Tag.Get("json")) // years
    }
    

    反射结构体

    为了完整说明反射的情况,通过反射一个结构体类型,综合来说明。下面例子较为系统地利用一个结构体,来充分举例说明反射:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    // 结构体
    type ss struct {
        int
        string
        bool
        float64
    }
    
    func (s ss) Method1(i int) string  { return "结构体方法1" }
    func (s *ss) Method2(i int) string { return "结构体方法2" }
    
    var (
        structValue = ss{ // 结构体
            20,
            "结构体",
            false,
            64.0,
        }
    )
    
    // 复杂类型
    var complexTypes = []interface{}{
        structValue, &structValue, // 结构体
        structValue.Method1, structValue.Method2, // 方法
    }
    
    func main() {
        // 测试复杂类型
        for i := 0; i < len(complexTypes); i++ {
            PrintInfo(complexTypes[i])
        }
    }
    
    func PrintInfo(i interface{}) {
        if i == nil {
            fmt.Println("--------------------")
            fmt.Printf("无效接口值:%v
    ", i)
            fmt.Println("--------------------")
            return
        }
        v := reflect.ValueOf(i)
        PrintValue(v)
    }
    
    func PrintValue(v reflect.Value) {
        fmt.Println("--------------------")
        // ----- 通用方法 -----
        fmt.Println("String             :", v.String())  // 反射值的字符串形式
        fmt.Println("Type               :", v.Type())    // 反射值的类型
        fmt.Println("Kind               :", v.Kind())    // 反射值的类别
        fmt.Println("CanAddr            :", v.CanAddr()) // 是否可以获取地址
        fmt.Println("CanSet             :", v.CanSet())  // 是否可以修改
        if v.CanAddr() {
            fmt.Println("Addr               :", v.Addr())       // 获取地址
            fmt.Println("UnsafeAddr         :", v.UnsafeAddr()) // 获取自由地址
        }
        // 获取方法数量
        fmt.Println("NumMethod          :", v.NumMethod())
        if v.NumMethod() > 0 {
            // 遍历方法
            i := 0
            for ; i < v.NumMethod()-1; i++ {
                fmt.Printf("    ┣ %v
    ", v.Method(i).String())
            }
            fmt.Printf("    ┗ %v
    ", v.Method(i).String())
            // 通过名称获取方法
            fmt.Println("MethodByName       :", v.MethodByName("String").String())
        }
    
        switch v.Kind() {
        // 结构体:
        case reflect.Struct:
            fmt.Println("=== 结构体 ===")
            // 获取字段个数
            fmt.Println("NumField           :", v.NumField())
            if v.NumField() > 0 {
                var i int
                // 遍历结构体字段
                for i = 0; i < v.NumField()-1; i++ {
                    field := v.Field(i) // 获取结构体字段
                    fmt.Printf("    ├ %-8v %v
    ", field.Type(), field.String())
                }
                field := v.Field(i) // 获取结构体字段
                fmt.Printf("    └ %-8v %v
    ", field.Type(), field.String())
                // 通过名称查找字段
                if v := v.FieldByName("ptr"); v.IsValid() {
                    fmt.Println("FieldByName(ptr)   :", v.Type().Name())
                }
                // 通过函数查找字段
                v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
                if v.IsValid() {
                    fmt.Println("FieldByNameFunc    :", v.Type().Name())
                }
            }
        }
    }
    

    输出结果:

    --------------------
    String             : <main.ss Value>
    Type               : main.ss
    Kind               : struct
    CanAddr            : false
    CanSet             : false
    NumMethod          : 1
        ┗ <func(int) string Value>
    MethodByName       : <invalid Value>
    === 结构体 ===
    NumField           : 4
        ├ int      <int Value>
        ├ string   结构体
        ├ bool     <bool Value>
        └ float64  <float64 Value>
    --------------------
    String             : <*main.ss Value>
    Type               : *main.ss
    Kind               : ptr
    CanAddr            : false
    CanSet             : false
    NumMethod          : 2
        ┣ <func(int) string Value>
        ┗ <func(int) string Value>
    MethodByName       : <invalid Value>
    --------------------
    String             : <func(int) string Value>
    Type               : func(int) string
    Kind               : func
    CanAddr            : false
    CanSet             : false
    NumMethod          : 0
    --------------------
    String             : <func(int) string Value>
    Type               : func(int) string
    Kind               : func
    CanAddr            : false
    CanSet             : false
    NumMethod          : 0
    
    

    structValue, &structValue的反射结果是不一样的,指针对象在这里有两个方法,而值对象只有一个方法,这是因为Method2()方法是指针方法,在值对象中是不能被反射到的。

    我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用:Method(n).Call(XXXX),具体传参怎么传再说。

  • 相关阅读:
    C# Redis实战(五)
    C# Redis实战(四)
    C# Redis实战(三)
    C# Redis实战(二)
    C# Redis实战(一)
    memcached的基本命令(安装、卸载、启动、配置相关)
    git和tortoisegit安装教程
    编程规范是非常重要的,为什么说可读性比什么都重要?你有没有确定一个编程规范呢?
    关于VR游戏的前景
    在项目开发过程中如何处理人际关系
  • 原文地址:https://www.cnblogs.com/ice-coder/p/12704250.html
Copyright © 2020-2023  润新知