Golang-学习笔记反射

35次阅读

共计 4711 个字符,预计需要花费 12 分钟才能阅读完成。

反射(Go Reflect)

在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的 状态 和相关的 语义

程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

反射就是在运行时动态的获取一个变量的类型信息和值信息。

每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang 实现了反射,反射机制就是在 运行时动态的调用对象的方法和属性,官方自带的 reflect 包 就是反射相关的,只要包含这个包就可以使用。

反射的缺点

不过反射也是把双刃剑,功能强大且代码可读性并不理想;若非必要,不推荐使用反射。

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

Reflect 包

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

  • reflect.TypeOf函数

    使用 reflect.TypeOf() 函数返回被检查对象的类型Type

    // 返回被检查对象的类型
    func reflectType(x interface{}){v := reflect.TypeOf(x)
        fmt.Println(v)
    }
    
    func main(){
         var a float32 = 3.14
         reflectType(a)
         var b int32 = 100
         reflectType(b)
         var c string = "小马"
         reflectType(c)
    }
    
    /*
    float32
    int32
    string
    */
  • type nametype kind

    在反射中关于类型的划分还分为两种:类型 Type 和底层类型 Kind。在 Go 中可以使用type 关键字声明多种自定义类型,而 kind 就是指底层的类型,但在反射中,需要区分指针、结构体等类型时,就需要用到底层类型kind

    例如,定义两个指针类型和两个结构体类型,通过反射 reflect 查看它们的类型和种类:

    type myInt int64
    // 反射
    func reflectType(x interface{}) {t := reflect.TypeOf(x)
        // 打印变量名和底层类型
        fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
    }
    
    // 调用 reflectType 返回类型
    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
        }
        var d = person{
            name: "完满主任",
            age:  18,
        }
        type class struct{student int}
        var e = class{88}
        reflectType(d) // type:person kind:struct
        reflectType(e) // type:class kind:struct
    }

    【注】:runeint32 的类型别名,同样的,byte也是 uint8 的类型别名。

    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        // 底层指针
    )
  • reflect.ValueOf函数

    使用 reflect.ValureOf 函数返回被检查对象的Value,其中包含原始值的类型信息,这两个值可以相互转换:

    // 通过 reflect.ValueOf 反射获取值
    func reflectValue(x interface{}){v := reflect.ValueOf(x)
        k := v.Kind()
        // 类型判断
        switch k {
        case reflect.Int64:
            // 从反射中获取整型的原始值,然后强转
            fmt.Printf("type is int64, value is %d\n",int64(v.Int()))
        case reflect.Float32:
            // 从反射中获取浮点型的原始值,然后强转
            fmt.Printf("type is float32, value is %f\n",float32(v.Float()))
        case reflect.Float64:
            // 从反射中获取浮点型的原始值,然后强转
            fmt.Printf("type is float64, value is %f\n",float64(v.Float()))
        case reflect.Bool:
            // 从反射中获取布尔型的原始值,然后强转
            fmt.Printf("type is bool, value is %t\n",bool(v.Bool()))
        default:
            fmt.Println("nil")
        }
    }
    
    func main(){
        var a float32 =  3.14
        var b int64 = 100
        var e bool = false
        reflectValue(a)
        reflectValue(b)
        reflectValue(e)
        // 将 int 类型的原始值转换为 reflect.Value 类型
        c := reflect.ValueOf(10)
        fmt.Printf("type c :%T\n", c)
        reflectValue(c)
    }
    
    /*
    type is float32, value is 3.140000
    type is int64, value is 100
    type is bool, value is false
    type c :reflect.Value
    nil
    */

    通过反射修改变量值

    想要通过反射修改变量的值,就要知道参数在函数之间传递是值类型还是引用类型,必须是传递变量的内存地址才能修改变量值。反射包 reflect 提供了一个 Elem() 方法返回指针对应的值:

    func reflectSetValue1 (x interface{}){v := reflect.ValueOf(x)
        if v.Kind() == reflect.Int64{v.SetInt(250) // 修改副本,会抛出 panic 异常
        }
    }
    
    func reflectSetValue2 (x interface{}){v := reflect.ValueOf(x)
        // 使用 Elem()方法修改变量的值
        if v.Elem().Kind() == reflect.Int64{v.Elem().SetInt(251)
        }
    }
    
    func main(){
        var a int64
        //reflectSetValue1(a)
        //panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
        reflectSetValue2(&a)
        fmt.Printf("type is int64, value is %d\n",a)
    }
    
    /*
    type is int64, value is 251
    */
    

    【注】:如果调用方法 reflectSetValue1(a),则编译器会抛出一个panic 异常

    结构体反射

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

### 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      // 是否匿名字段
}

### 结构体反射(demo)

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

type student struct {
    Name string `json:"name"` 
    Score int `json:"score"`
}
func main(){
    // 初始化
    stu1 := student{ 
        Name:  "小马",
        Score: 99,
    }
    // 返回类型
    t := reflect.TypeOf(stu1)
    fmt.Printf("name:%s kind:%v\n",t.Name(),t.Kind())
    // 通过 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"))
    }
}

/*
name:student kind:struct
name:Name index:[0] type:string json tag:name
name:Score index:[1] type:int json tag:score
name:Score index:[1] type:int json tag:score
*/

正文完
 0