Golang 学习笔记:反射

sunlingbot · · 520 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

反射(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
*/

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:sunlingbot

查看原文:Golang 学习笔记:反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

520 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传